Explorar el Código

Merge pull request #5 from 9seconds/tgproxies

Refactoring
tags/0.9
Sergey Arkhipov hace 7 años
padre
commit
08ea80680c
No account linked to committer's email address

+ 4
- 3
Dockerfile Ver fichero

@@ -10,6 +10,7 @@ RUN set -x \
10 10
     curl \
11 11
     git \
12 12
     make \
13
+    upx \
13 14
   && update-ca-certificates
14 15
 
15 16
 ADD . /go/src/github.com/9seconds/mtg
@@ -17,7 +18,8 @@ ADD . /go/src/github.com/9seconds/mtg
17 18
 RUN set -x \
18 19
   && cd /go/src/github.com/9seconds/mtg \
19 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,8 +31,7 @@ ENTRYPOINT ["/usr/local/bin/mtg"]
29 31
 ENV MTG_IP=0.0.0.0 \
30 32
     MTG_PORT=3128 \
31 33
     MTG_STATS_IP=0.0.0.0 \
32
-    MTG_STATS_PORT=3129 \
33
-    MTG_USE_IPV6=true
34
+    MTG_STATS_PORT=3129
34 35
 EXPOSE 3128 3129
35 36
 
36 37
 COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

+ 2
- 2
Makefile Ver fichero

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

+ 31
- 103
README.md Ver fichero

@@ -8,109 +8,35 @@ Bullshit-free MTPROTO proxy for Telegram
8 8
 
9 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 42
 # How to build
@@ -170,3 +96,5 @@ $ docker run --name mtg --restart=unless-stopped -p 444:3128 -p 3129:3129 -d nin
170 96
 You will have this tool up and running on port 444. Now curl
171 97
 `localhost:3129` to get `tg://` links or do `docker logs mtg`. Also,
172 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 Ver fichero

@@ -0,0 +1,11 @@
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 Ver fichero

@@ -0,0 +1,30 @@
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 Ver fichero

@@ -0,0 +1,142 @@
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 Ver fichero

@@ -0,0 +1,39 @@
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 Ver fichero

@@ -0,0 +1,59 @@
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 Ver fichero

@@ -3,18 +3,16 @@ package main
3 3
 //go:generate scripts/generate_version.sh
4 4
 
5 5
 import (
6
-	"encoding/hex"
7 6
 	"encoding/json"
8 7
 	"io"
9
-	"io/ioutil"
10
-	"net/http"
11 8
 	"os"
12
-	"strings"
13 9
 
14
-	"github.com/9seconds/mtg/proxy"
15 10
 	"go.uber.org/zap"
16 11
 	"go.uber.org/zap/zapcore"
17 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 18
 var (
@@ -28,8 +26,9 @@ var (
28 26
 		Short('v').
29 27
 		Envar("MTG_VERBOSE").
30 28
 		Bool()
29
+
31 30
 	bindIP = app.Flag("bind-ip", "Which IP to bind to.").
32
-		Short('i').
31
+		Short('b').
33 32
 		Envar("MTG_IP").
34 33
 		Default("127.0.0.1").
35 34
 		IP()
@@ -38,11 +37,23 @@ var (
38 37
 			Envar("MTG_PORT").
39 38
 			Default("3128").
40 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 57
 	statsIP = app.Flag("stats-ip", "Which IP bind stats server to").
47 58
 		Short('t').
48 59
 		Envar("MTG_STATS_IP").
@@ -53,6 +64,7 @@ var (
53 64
 			Envar("MTG_STATS_PORT").
54 65
 			Default("3129").
55 66
 			Uint16()
67
+
56 68
 	readTimeout = app.Flag("read-timeout", "Socket read timeout.").
57 69
 			Short('r').
58 70
 			Envar("MTG_READ_TIMEOUT").
@@ -63,15 +75,6 @@ var (
63 75
 			Envar("MTG_WRITE_TIMEOUT").
64 76
 			Default("30s").
65 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 79
 	secret = app.Arg("secret", "Secret of this proxy.").Required().String()
77 80
 )
@@ -80,33 +83,22 @@ func main() {
80 83
 	app.Version(version)
81 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 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 98
 	atom := zap.NewAtomicLevel()
107
-	if *debug {
99
+	if conf.Debug {
108 100
 		atom.SetLevel(zapcore.DebugLevel)
109
-	} else if *verbose {
101
+	} else if conf.Verbose {
110 102
 		atom.SetLevel(zapcore.InfoLevel)
111 103
 	} else {
112 104
 		atom.SetLevel(zapcore.ErrorLevel)
@@ -118,12 +110,12 @@ func main() {
118 110
 		atom,
119 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 119
 	if err := srv.Serve(); err != nil {
128 120
 		logger.Fatal(err.Error())
129 121
 	}

+ 1
- 7
obfuscated2/frame.go Ver fichero

@@ -58,13 +58,7 @@ func (f Frame) DC() (n int16) {
58 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 64
 // Valid checks that *decrypted* frame is valid. Only magic bytes are checked.

+ 1
- 1
obfuscated2/frame_test.go Ver fichero

@@ -34,7 +34,7 @@ func TestFrameMagic(t *testing.T) {
34 34
 }
35 35
 
36 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 40
 func TestFrameValid(t *testing.T) {

+ 7
- 23
obfuscated2/obfuscated2.go Ver fichero

@@ -11,31 +11,15 @@ import (
11 11
 // Obfuscated2 contains AES CTR encryption and decryption streams
12 12
 // for telegram connection.
13 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 18
 // ParseObfuscated2ClientFrame parses client frame. Please check this link for
33 19
 // details: http://telegra.ph/telegram-blocks-wtf-05-26
34 20
 //
35 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 23
 	decHasher := sha256.New()
40 24
 	decHasher.Write(frame.Key()) // nolint: errcheck
41 25
 	decHasher.Write(secret)      // nolint: errcheck
@@ -54,8 +38,8 @@ func ParseObfuscated2ClientFrame(secret, data []byte) (*Obfuscated2, int16, erro
54 38
 	}
55 39
 
56 40
 	obfs := &Obfuscated2{
57
-		decryptor: decryptor,
58
-		encryptor: encryptor,
41
+		Decryptor: decryptor,
42
+		Encryptor: encryptor,
59 43
 	}
60 44
 
61 45
 	return obfs, decryptedFrame.DC(), nil
@@ -77,8 +61,8 @@ func MakeTelegramObfuscated2Frame() (*Obfuscated2, Frame) {
77 61
 	copy(frame, copyFrame)
78 62
 
79 63
 	obfs := &Obfuscated2{
80
-		decryptor: decryptor,
81
-		encryptor: encryptor,
64
+		Decryptor: decryptor,
65
+		Encryptor: encryptor,
82 66
 	}
83 67
 
84 68
 	return obfs, frame

+ 6
- 3
obfuscated2/obfuscated2_test.go Ver fichero

@@ -25,7 +25,8 @@ func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) {
25 25
 	data := []byte{1, 2, 3}
26 26
 	encrypted := make([]byte, 3)
27 27
 	encryptor.XORKeyStream(encrypted, data)
28
-	decrypted := obfs2.Decrypt(encrypted)
28
+	decrypted := make([]byte, 3)
29
+	obfs2.Decryptor.XORKeyStream(decrypted, encrypted)
29 30
 
30 31
 	assert.Equal(t, data, decrypted)
31 32
 }
@@ -67,10 +68,12 @@ func TestObfs2Full(t *testing.T) {
67 68
 	tgEncryptedMessage := make([]byte, len(message))
68 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 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 77
 	finalMessage := make([]byte, len(clientEncryptedMessage))
75 78
 	clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage)
76 79
 

+ 0
- 56
proxy/cipherrwc.go Ver fichero

@@ -1,56 +0,0 @@
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 Ver fichero

@@ -4,33 +4,30 @@ import (
4 4
 	"context"
5 5
 	"io"
6 6
 	"net"
7
-	"strconv"
8 7
 	"sync"
9
-	"time"
10 8
 
11
-	"github.com/9seconds/mtg/obfuscated2"
12 9
 	"github.com/juju/errors"
13 10
 	uuid "github.com/satori/go.uuid"
14 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 19
 // Server is an insgtance of MTPROTO proxy.
18 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 28
 // Serve does MTPROTO proxying.
31 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 31
 	if err != nil {
35 32
 		return errors.Annotate(err, "Cannot create listen socket")
36 33
 	}
@@ -56,18 +53,16 @@ func (s *Server) accept(conn net.Conn) {
56 53
 
57 54
 	s.stats.newConnection()
58 55
 	ctx, cancel := context.WithCancel(context.Background())
59
-	socketID := s.makeSocketID()
56
+	socketID := uuid.NewV4().String()
60 57
 
61 58
 	s.logger.Debugw("Client connected",
62
-		"secret", s.secret,
63 59
 		"addr", conn.RemoteAddr().String(),
64 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 64
 	if err != nil {
69 65
 		s.logger.Warnw("Cannot initialize client connection",
70
-			"secret", s.secret,
71 66
 			"addr", conn.RemoteAddr().String(),
72 67
 			"socketid", socketID,
73 68
 			"error", err,
@@ -100,68 +95,49 @@ func (s *Server) accept(conn net.Conn) {
100 95
 	wait.Wait()
101 96
 
102 97
 	s.logger.Debugw("Client disconnected",
103
-		"secret", s.secret,
104 98
 		"addr", conn.RemoteAddr().String(),
105 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 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 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 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 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 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 Ver fichero

@@ -2,13 +2,12 @@ package proxy
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"fmt"
6
-	"net"
7 5
 	"net/http"
8
-	"net/url"
9 6
 	"strconv"
10 7
 	"sync/atomic"
11 8
 	"time"
9
+
10
+	"github.com/9seconds/mtg/config"
12 11
 )
13 12
 
14 13
 type statsUptime time.Time
@@ -26,13 +25,10 @@ type Stats struct {
26 25
 		Incoming uint64 `json:"incoming"`
27 26
 		Outgoing uint64 `json:"outgoing"`
28 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 34
 func (s *Stats) newConnection() {
@@ -53,7 +49,7 @@ func (s *Stats) addOutgoingTraffic(n int) {
53 49
 }
54 50
 
55 51
 // Serve runs statistics HTTP server.
56
-func (s *Stats) Serve(host fmt.Stringer, port uint16) {
52
+func (s *Stats) Serve() {
57 53
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
58 54
 		w.Header().Set("Content-Type", "application/json")
59 55
 
@@ -63,65 +59,16 @@ func (s *Stats) Serve(host fmt.Stringer, port uint16) {
63 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 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 Ver fichero

@@ -1,75 +0,0 @@
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 Ver fichero

@@ -0,0 +1,36 @@
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 Ver fichero

@@ -0,0 +1,53 @@
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 Ver fichero

@@ -0,0 +1,61 @@
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 Ver fichero

@@ -0,0 +1,41 @@
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 Ver fichero

@@ -1,4 +1,4 @@
1
-package proxy
1
+package wrappers
2 2
 
3 3
 import (
4 4
 	"context"
@@ -48,7 +48,9 @@ func (c *CtxReadWriteCloser) Close() error {
48 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 54
 	return &CtxReadWriteCloser{
53 55
 		conn:   conn,
54 56
 		ctx:    ctx,

proxy/logrwc.go → wrappers/logrwc.go Ver fichero

@@ -1,4 +1,4 @@
1
-package proxy
1
+package wrappers
2 2
 
3 3
 import (
4 4
 	"io"
@@ -36,7 +36,8 @@ func (l *LogReadWriteCloser) Close() error {
36 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 41
 	return &LogReadWriteCloser{
41 42
 		conn:   conn,
42 43
 		logger: logger,

+ 54
- 0
wrappers/streamcipherrwc.go Ver fichero

@@ -0,0 +1,54 @@
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 Ver fichero

@@ -1,4 +1,4 @@
1
-package proxy
1
+package wrappers
2 2
 
3 3
 import (
4 4
 	"io"
@@ -31,7 +31,9 @@ func (t *TimeoutReadWriteCloser) Close() error {
31 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 37
 	return &TimeoutReadWriteCloser{
36 38
 		conn:         conn,
37 39
 		readTimeout:  readTimeout,

proxy/trafficrwc.go → wrappers/trafficrwc.go Ver fichero

@@ -1,4 +1,4 @@
1
-package proxy
1
+package wrappers
2 2
 
3 3
 import "io"
4 4
 
@@ -29,7 +29,8 @@ func (t *TrafficReadWriteCloser) Close() error {
29 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 34
 	return &TrafficReadWriteCloser{
34 35
 		conn:          conn,
35 36
 		readCallback:  readCallback,

Loading…
Cancelar
Guardar