Sfoglia il codice sorgente

Add cli command for access

tags/v2.0.0-rc1
9seconds 5 anni fa
parent
commit
cbcc113e41
6 ha cambiato i file con 242 aggiunte e 2 eliminazioni
  1. 156
    0
      cli_access.go
  2. 8
    0
      config.go
  3. 6
    0
      example.config.toml
  4. 6
    2
      main.go
  5. 2
    0
      mtglib/network/init.go
  6. 64
    0
      utils.go

+ 156
- 0
cli_access.go Vedi File

1
+package main
2
+
3
+import (
4
+	"context"
5
+	"encoding/json"
6
+	"io/ioutil"
7
+	"net"
8
+	"net/http"
9
+	"net/url"
10
+	"os"
11
+	"strconv"
12
+	"strings"
13
+
14
+	"github.com/9seconds/mtg/v2/mtglib/network"
15
+)
16
+
17
+type runAccessResponse struct {
18
+	IPv4   *runAccessResponseURLs `json:"ipv4,omitempty"`
19
+	IPv6   *runAccessResponseURLs `json:"ipv6,omitempty"`
20
+	Secret struct {
21
+		Hex    string `json:"hex"`
22
+		Base64 string `json:"base64"`
23
+	} `json:"secret"`
24
+}
25
+
26
+type runAccessResponseURLs struct {
27
+	IP        net.IP `json:"ip"`
28
+	TgURL     string `json:"tg_url"`
29
+	TgQrCode  string `json:"tg_qrcode"`
30
+	TmeURL    string `json:"tme_url"`
31
+	TmeQrCode string `json:"tme_qrcode"`
32
+}
33
+
34
+func runAccess(cli *CLI) {
35
+	filefp, err := os.Open(cli.Access.ConfigPath)
36
+	if err != nil {
37
+		exit(err)
38
+	}
39
+
40
+	defer filefp.Close()
41
+
42
+	conf, err := parseConfig(filefp)
43
+	if err != nil {
44
+		exit(err)
45
+	}
46
+
47
+	ntw, err := makeNetwork(conf)
48
+	if err != nil {
49
+		exit(err)
50
+	}
51
+
52
+	ipv4 := conf.Network.PublicIP.IPv4.Value(nil)
53
+	ipv6 := conf.Network.PublicIP.IPv6.Value(nil)
54
+
55
+	if ipv4 == nil {
56
+		ipv4 = runAccessGetIP(ntw, "tcp4")
57
+	}
58
+
59
+	if ipv6 == nil {
60
+		ipv6 = runAccessGetIP(ntw, "tcp6")
61
+	}
62
+
63
+	resp := runAccessResponse{
64
+		IPv4: runMakeAccessResponseURLs(ipv4, conf, cli),
65
+		IPv6: runMakeAccessResponseURLs(ipv6, conf, cli),
66
+	}
67
+    resp.Secret.Base64 = conf.Secret.Base64()
68
+    resp.Secret.Hex = conf.Secret.EE()
69
+
70
+	encoder := json.NewEncoder(os.Stdout)
71
+
72
+	encoder.SetEscapeHTML(false)
73
+	encoder.SetIndent("", "  ")
74
+
75
+	if err := encoder.Encode(resp); err != nil {
76
+		exit(err)
77
+	}
78
+}
79
+
80
+func runAccessGetIP(ntw *network.Network, protocol string) net.IP {
81
+	client := &http.Client{
82
+		Timeout: ntw.HTTP.Timeout,
83
+		Transport: &http.Transport{
84
+			DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
85
+				return ntw.DialContext(ctx, protocol, address)
86
+			},
87
+		},
88
+	}
89
+
90
+	resp, err := client.Get("https://ifconfig.co")
91
+	if err != nil {
92
+		return nil
93
+	}
94
+
95
+	defer exhaustResponse(resp)
96
+
97
+	data, err := ioutil.ReadAll(resp.Body)
98
+	if err != nil {
99
+		return nil
100
+	}
101
+
102
+	return net.ParseIP(strings.TrimSpace(string(data)))
103
+}
104
+
105
+func runMakeAccessResponseURLs(ip net.IP, conf *config, cli *CLI) *runAccessResponseURLs {
106
+	if ip == nil {
107
+		return nil
108
+	}
109
+
110
+	values := url.Values{}
111
+
112
+	values.Set("server", ip.String())
113
+	values.Set("port", strconv.Itoa(int(conf.BindTo.port.Value(0))))
114
+	values.Set("secret", conf.Secret.Base64())
115
+
116
+	if cli.Access.Hex {
117
+		values.Set("secret", conf.Secret.EE())
118
+	}
119
+
120
+	urlQuery := values.Encode()
121
+
122
+	rv := &runAccessResponseURLs{
123
+		IP: ip,
124
+		TgURL: (&url.URL{
125
+			Scheme:   "tg",
126
+			Host:     "proxy",
127
+			RawQuery: urlQuery,
128
+		}).String(),
129
+		TmeURL: (&url.URL{
130
+			Scheme:   "https",
131
+			Host:     "t.me",
132
+			Path:     "proxy",
133
+			RawQuery: urlQuery,
134
+		}).String(),
135
+	}
136
+
137
+	rv.TgQrCode = runMakeAccessResponseURLsQRCode(rv.TgURL)
138
+	rv.TmeQrCode = runMakeAccessResponseURLsQRCode(rv.TmeURL)
139
+
140
+	return rv
141
+}
142
+
143
+func runMakeAccessResponseURLsQRCode(data string) string {
144
+	values := url.Values{}
145
+
146
+	values.Set("qzone", "4")
147
+	values.Set("format", "svg")
148
+	values.Set("data", data)
149
+
150
+	return (&url.URL{
151
+		Scheme:   "https",
152
+		Host:     "api.qrserver.com",
153
+		Path:     "v1/create-qr-code",
154
+		RawQuery: values.Encode(),
155
+	}).String()
156
+}

+ 8
- 0
config.go Vedi File

410
 			IPv4 configTypeIP `json:"ipv4"`
410
 			IPv4 configTypeIP `json:"ipv4"`
411
 			IPv6 configTypeIP `json:"ipv6"`
411
 			IPv6 configTypeIP `json:"ipv6"`
412
 		} `json:"public-ip"`
412
 		} `json:"public-ip"`
413
+		Timeout struct {
414
+			TCP  configTypeDuration `json:"tcp"`
415
+			HTTP configTypeDuration `json:"http"`
416
+		} `json:"timeout"`
413
 		DOHIP   configTypeIP    `json:"doh-ip"`
417
 		DOHIP   configTypeIP    `json:"doh-ip"`
414
 		Proxies []configTypeURL `json:"proxies"`
418
 		Proxies []configTypeURL `json:"proxies"`
415
 	} `json:"network"`
419
 	} `json:"network"`
472
 			IPv4 string `toml:"ipv4" json:"ipv4"`
476
 			IPv4 string `toml:"ipv4" json:"ipv4"`
473
 			IPv6 string `toml:"ipv6" json:"ipv6"`
477
 			IPv6 string `toml:"ipv6" json:"ipv6"`
474
 		} `toml:"public-ip" json:"public-ip"`
478
 		} `toml:"public-ip" json:"public-ip"`
479
+		Timeout struct {
480
+			TCP  string `toml:"tcp" json:"tcp"`
481
+			HTTP string `toml:"http" json:"http"`
482
+		} `toml:"timeout" json:"timeout"`
475
 		DOHIP   string   `toml:"doh-ip" json:"doh-ip"`
483
 		DOHIP   string   `toml:"doh-ip" json:"doh-ip"`
476
 		Proxies []string `toml:"proxies" json:"proxies"`
484
 		Proxies []string `toml:"proxies" json:"proxies"`
477
 	} `toml:"network" json:"network"`
485
 	} `toml:"network" json:"network"`

+ 6
- 0
example.config.toml Vedi File

101
 ipv4 = ""
101
 ipv4 = ""
102
 ipv6 = ""
102
 ipv6 = ""
103
 
103
 
104
+# network timeouts define different settings for timeouts. HTTP timeout
105
+# is required only for DOH.
106
+[network.timeout]
107
+tcp = "5s"
108
+http = "10s"
109
+
104
 # FakeTLS can compare timestamps to prevent probes. Each message has
110
 # FakeTLS can compare timestamps to prevent probes. Each message has
105
 # encrypted timestamp. So, mtg can compare this timestamp and decide if
111
 # encrypted timestamp. So, mtg can compare this timestamp and decide if
106
 # we need to proceed with connection or not.
112
 # we need to proceed with connection or not.

+ 6
- 2
main.go Vedi File

16
 	} `cmd help:"Generate new proxy secret."`
16
 	} `cmd help:"Generate new proxy secret."`
17
 	Access struct {
17
 	Access struct {
18
 		ConfigPath string `arg required type:"existingfile" help:"Path to the configuration file." name:"config-path"`
18
 		ConfigPath string `arg required type:"existingfile" help:"Path to the configuration file." name:"config-path"`
19
+		Hex        bool   `help:"Print secret in hex encoding."`
19
 	} `cmd help:"Print access information."`
20
 	} `cmd help:"Print access information."`
20
 	Run struct {
21
 	Run struct {
21
 		ConfigPath string `arg required type:"existingfile" help:"Path to the configuration file." name:"config-path"`
22
 		ConfigPath string `arg required type:"existingfile" help:"Path to the configuration file." name:"config-path"`
34
 	switch ctx.Command() {
35
 	switch ctx.Command() {
35
 	case "generate-secret":
36
 	case "generate-secret":
36
 		runGenerateSecret(cli)
37
 		runGenerateSecret(cli)
37
-	case "access":
38
-	case "run":
38
+	case "access <config-path>":
39
+		runAccess(cli)
40
+	case "run <config-path>":
39
 		panic("not implemented yet")
41
 		panic("not implemented yet")
42
+	default:
43
+		panic(ctx.Command())
40
 	}
44
 	}
41
 }
45
 }

+ 2
- 0
mtglib/network/init.go Vedi File

16
 	ProxyDialerOpenThreshold        = 5
16
 	ProxyDialerOpenThreshold        = 5
17
 	ProxyDialerHalfOpenTimeout      = time.Minute
17
 	ProxyDialerHalfOpenTimeout      = time.Minute
18
 	ProxyDialerResetFailuresTimeout = 10 * time.Second
18
 	ProxyDialerResetFailuresTimeout = 10 * time.Second
19
+
20
+	DefaultDOHHostname = "9.9.9.9"
19
 )
21
 )
20
 
22
 
21
 var (
23
 var (

+ 64
- 0
utils.go Vedi File

1
+package main
2
+
3
+import (
4
+	"fmt"
5
+	"io"
6
+	"io/ioutil"
7
+	"net"
8
+	"net/http"
9
+	"net/url"
10
+	"os"
11
+
12
+	"github.com/9seconds/mtg/v2/mtglib/network"
13
+)
14
+
15
+func exit(err error) {
16
+	fmt.Fprintln(os.Stderr, err.Error())
17
+	os.Exit(1)
18
+}
19
+
20
+func makeNetwork(conf *config) (*network.Network, error) {
21
+	tcpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultTimeout)
22
+	httpTimeout := conf.Network.Timeout.TCP.Value(network.DefaultHTTPTimeout)
23
+	dohIP := conf.Network.DOHIP.Value(net.ParseIP(network.DefaultDOHHostname)).String()
24
+	bufferSize := conf.TCPBuffer.Value(network.DefaultBufferSize)
25
+
26
+	baseDialer, err := network.NewDefaultDialer(tcpTimeout, int(bufferSize))
27
+	if err != nil {
28
+		return nil, fmt.Errorf("cannot build a default dialer: %w", err)
29
+	}
30
+
31
+	proxyURLs := make([]*url.URL, len(conf.Network.Proxies))
32
+
33
+	for _, v := range conf.Network.Proxies {
34
+		if value := v.Value(nil); value != nil {
35
+			proxyURLs = append(proxyURLs, v.Value(nil))
36
+		}
37
+	}
38
+
39
+	switch len(proxyURLs) {
40
+	case 0:
41
+		return network.NewNetwork(baseDialer, dohIP, httpTimeout)
42
+	case 1:
43
+		socksDialer, err := network.NewSocks5Dialer(baseDialer, proxyURLs[0])
44
+
45
+		if err != nil {
46
+			return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
47
+		}
48
+
49
+		return network.NewNetwork(socksDialer, dohIP, httpTimeout)
50
+	}
51
+
52
+	socksDialer, err := network.NewLoadBalancedSocks5Dialer(baseDialer, proxyURLs)
53
+	if err != nil {
54
+
55
+		return nil, fmt.Errorf("cannot build socks5 dialer: %w", err)
56
+	}
57
+
58
+	return network.NewNetwork(socksDialer, dohIP, httpTimeout)
59
+}
60
+
61
+func exhaustResponse(response *http.Response) {
62
+	io.Copy(ioutil.Discard, response.Body)
63
+	response.Body.Close()
64
+}

Loading…
Annulla
Salva