From 3816dbf5b1b85245f1f6cecc4c10c195f1af8c9f Mon Sep 17 00:00:00 2001 From: 9seconds Date: Mon, 9 Sep 2019 10:25:38 +0300 Subject: [PATCH] Add telegram api --- conntypes/id.go | 24 +++++++++ go.mod | 1 + go.sum | 2 + protocol/request.go | 3 +- proxy/proxy.go | 3 +- telegram/api/addresses.go | 106 ++++++++++++++++++++++++++++++++++++++ telegram/api/api.go | 38 ++++++++++++++ telegram/api/secret.go | 23 +++++++++ wrappers/wrapper_conn.go | 29 ++--------- 9 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 conntypes/id.go create mode 100644 telegram/api/addresses.go create mode 100644 telegram/api/api.go create mode 100644 telegram/api/secret.go diff --git a/conntypes/id.go b/conntypes/id.go new file mode 100644 index 0000000..1aa482c --- /dev/null +++ b/conntypes/id.go @@ -0,0 +1,24 @@ +package conntypes + +import ( + "crypto/rand" + "encoding/hex" +) + +const ConnIDLength = 8 + +type ConnID [ConnIDLength]byte + +func (c ConnID) String() string { + return hex.EncodeToString(c[:]) +} + +func NewConnID() ConnID { + var id ConnID + + if _, err := rand.Read(id[:]); err != nil { + panic(err) + } + + return id +} diff --git a/go.mod b/go.mod index e2a1821..4387fe3 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/beevik/ntp v0.2.0 github.com/cespare/xxhash v1.1.0 + github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d github.com/kr/pretty v0.1.0 // indirect github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v1.1.0 diff --git a/go.sum b/go.sum index 0ffeeaf..9b41aba 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= +github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= diff --git a/protocol/request.go b/protocol/request.go index ce8728d..e73489d 100644 --- a/protocol/request.go +++ b/protocol/request.go @@ -5,13 +5,14 @@ import ( "go.uber.org/zap" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/wrappers" ) type TelegramRequest struct { Logger *zap.SugaredLogger ClientConn wrappers.StreamReadWriteCloser - ConnID wrappers.ConnID + ConnID conntypes.ConnID Ctx context.Context Cancel context.CancelFunc ClientProtocol ClientProtocol diff --git a/proxy/proxy.go b/proxy/proxy.go index 7ead6d9..e4d910f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -9,6 +9,7 @@ import ( "go.uber.org/zap" "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/telegram" @@ -53,7 +54,7 @@ func (p *Proxy) accept(conn net.Conn) { } }() - connID := wrappers.NewConnID() + connID := conntypes.NewConnID() logger := p.Logger.With("connection_id", connID) if err := utils.InitTCP(conn); err != nil { diff --git a/telegram/api/addresses.go b/telegram/api/addresses.go new file mode 100644 index 0000000..f65fcac --- /dev/null +++ b/telegram/api/addresses.go @@ -0,0 +1,106 @@ +package api + +import ( + "bufio" + "fmt" + "net" + "regexp" + "strconv" + "strings" + + "github.com/9seconds/mtg/conntypes" +) + +const ( + addressesURLV4 = "https://core.telegram.org/getProxyConfig" // nolint: gas + addressesURLV6 = "https://core.telegram.org/getProxyConfigV6" // nolint: gas +) + +var addressesProxyForSplitter = regexp.MustCompile(`\s+`) + +func AddressesV4() (map[conntypes.DC][]string, conntypes.DC, error) { + return getAddresses(addressesURLV4) +} + +func AddressesV6() (map[conntypes.DC][]string, conntypes.DC, error) { + return getAddresses(addressesURLV6) +} + +func getAddresses(url string) (map[conntypes.DC][]string, conntypes.DC, error) { + resp, err := request(url) + if err != nil { + return nil, 0, fmt.Errorf("cannot get http response: %w", err) + } + defer resp.Close() + + scanner := bufio.NewScanner(resp) + data := map[conntypes.DC][]string{} + + var defaultDC = conntypes.DCDefaultIdx + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + switch { + case strings.HasPrefix(text, "#"): + continue + + case strings.HasPrefix(text, "proxy_for"): + addr, idx, err := addressesParseProxyFor(text) + if err != nil { + return nil, 0, fmt.Errorf("cannot parse 'proxy_for' section: %w", err) + } + if addresses, ok := data[idx]; ok { + data[idx] = append(addresses, addr) + } else { + data[idx] = []string{addr} + } + + case strings.HasPrefix(text, "default"): + idx, err := addressesParseDefault(text) + if err != nil { + return nil, 0, fmt.Errorf("cannot parse 'default' section: %w", err) + } + defaultDC = idx + } + } + + err = scanner.Err() + if err != nil { + return nil, 0, fmt.Errorf("cannot parse http response: %w", err) + } + + return data, defaultDC, nil +} + +func addressesParseProxyFor(text string) (string, conntypes.DC, error) { + chunks := addressesProxyForSplitter.Split(text, 3) + if len(chunks) != 3 || chunks[0] != "proxy_for" { + return "", 0, fmt.Errorf("incorrect config %s", text) + } + + dc, err := strconv.ParseInt(chunks[1], 10, 16) + if err != nil { + return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err) + } + + addr := strings.TrimRight(chunks[2], ";") + if _, _, err = net.SplitHostPort(addr); err != nil { + return "", 0, fmt.Errorf("incorrect config '%s': %w", text, err) + } + + return addr, conntypes.DC(dc), nil +} + +func addressesParseDefault(text string) (conntypes.DC, error) { + chunks := addressesProxyForSplitter.Split(text, 2) + if len(chunks) != 2 || chunks[0] != "default" { + return 0, fmt.Errorf("incorrect config '%s'", text) + } + + dcString := strings.TrimRight(chunks[1], ";") + dc, err := strconv.ParseInt(dcString, 10, 16) + if err != nil { + return 0, fmt.Errorf("incorrect config '%s': %w", text, err) + } + + return conntypes.DC(dc), nil +} diff --git a/telegram/api/api.go b/telegram/api/api.go new file mode 100644 index 0000000..8b36d83 --- /dev/null +++ b/telegram/api/api.go @@ -0,0 +1,38 @@ +package api + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "time" +) + +const ( + apiUserAgent = "mtg" + apiHTTPTimeout = 30 * time.Second +) + +var httpClient = http.Client{ + Timeout: apiHTTPTimeout, +} + +func request(url string) (io.ReadCloser, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + panic(err) + } + req.Header.Set("Accept", "text/plan") + req.Header.Set("User-Agent", apiUserAgent) + + resp, err := httpClient.Do(req) + if err != nil { + if resp != nil { + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + } + return nil, fmt.Errorf("cannot perform a request: %w", err) + } + + return resp.Body, err +} diff --git a/telegram/api/secret.go b/telegram/api/secret.go new file mode 100644 index 0000000..c2b2e43 --- /dev/null +++ b/telegram/api/secret.go @@ -0,0 +1,23 @@ +package api + +import ( + "fmt" + "io/ioutil" +) + +const secretURL = "https://core.telegram.org/getProxySecret" // nolint: gas + +func Secret() ([]byte, error) { + resp, err := request(secretURL) + if err != nil { + return nil, fmt.Errorf("cannot access telegram server: %w", err) + } + defer resp.Close() + + secret, err := ioutil.ReadAll(resp) + if err != nil { + return nil, fmt.Errorf("cannot read response: %w", err) + } + + return secret, nil +} diff --git a/wrappers/wrapper_conn.go b/wrappers/wrapper_conn.go index 19fd9f6..1649cd1 100644 --- a/wrappers/wrapper_conn.go +++ b/wrappers/wrapper_conn.go @@ -2,8 +2,6 @@ package wrappers import ( "context" - "crypto/rand" - "encoding/hex" "fmt" "net" "time" @@ -11,16 +9,9 @@ import ( "go.uber.org/zap" "github.com/9seconds/mtg/config" + "github.com/9seconds/mtg/conntypes" ) -const ConnIDLength = 8 - -type ConnID [ConnIDLength]byte - -func (c ConnID) String() string { - return hex.EncodeToString(c[:]) -} - type connPurpose uint8 const ( @@ -37,7 +28,7 @@ type wrapperConn struct { parent net.Conn ctx context.Context cancel context.CancelFunc - connID ConnID + connID conntypes.ConnID logger *zap.SugaredLogger localAddr *net.TCPAddr remoteAddr *net.TCPAddr @@ -121,7 +112,7 @@ func (w *wrapperConn) RemoteAddr() *net.TCPAddr { func newConn(ctx context.Context, cancel context.CancelFunc, parent net.Conn, - connID ConnID, + connID conntypes.ConnID, purpose connPurpose) StreamReadWriteCloser { localAddr := *parent.LocalAddr().(*net.TCPAddr) @@ -156,22 +147,12 @@ func newConn(ctx context.Context, func NewClientConn(ctx context.Context, cancel context.CancelFunc, parent net.Conn, - connID ConnID) StreamReadWriteCloser { + connID conntypes.ConnID) StreamReadWriteCloser { return newConn(ctx, cancel, parent, connID, connPurposeClient) } func NewTelegramConn(ctx context.Context, cancel context.CancelFunc, parent net.Conn) StreamReadWriteCloser { - return newConn(ctx, cancel, parent, ConnID{}, connPurposeTelegram) -} - -func NewConnID() ConnID { - var id ConnID - - if _, err := rand.Read(id[:]); err != nil { - panic(err) - } - - return id + return newConn(ctx, cancel, parent, conntypes.ConnID{}, connPurposeTelegram) }