mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 15:54:03 +03:00
Direct proxy works
This commit is contained in:
+230
-196
@@ -2,223 +2,257 @@ package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"go.uber.org/zap"
|
||||
statsd "gopkg.in/alexcesaro/statsd.v2"
|
||||
)
|
||||
|
||||
// Config represents common configuration of mtg.
|
||||
type SecretMode uint8
|
||||
|
||||
func (s SecretMode) String() string {
|
||||
switch s {
|
||||
case SecretModeSimple:
|
||||
return "simple"
|
||||
case SecretModeSecured:
|
||||
return "secured"
|
||||
}
|
||||
return "tls"
|
||||
}
|
||||
|
||||
const (
|
||||
SecretModeSimple SecretMode = iota
|
||||
SecretModeSecured
|
||||
SecretModeTLS
|
||||
)
|
||||
|
||||
const SimpleSecretLength = 16
|
||||
|
||||
type OptionType uint8
|
||||
|
||||
const (
|
||||
OptionTypeDebug OptionType = iota
|
||||
OptionTypeVerbose
|
||||
|
||||
OptionTypeBindIP
|
||||
OptionTypeBindPort
|
||||
OptionTypePublicIPv4
|
||||
OptionTypePublicIPv4Port
|
||||
OptionTypePublicIPv6
|
||||
OptionTypePublicIPv6Port
|
||||
OptionTypeStatsIP
|
||||
OptionTypeStatsPort
|
||||
|
||||
OptionTypeStatsdIP
|
||||
OptionTypeStatsdPort
|
||||
OptionTypeStatsdNetwork
|
||||
OptionTypeStatsdPrefix
|
||||
OptionTypeStatsdTagsFormat
|
||||
OptionTypeStatsdTags
|
||||
OptionTypePrometheusPrefix
|
||||
|
||||
OptionTypeWriteBufferSize
|
||||
OptionTypeReadBufferSize
|
||||
|
||||
OptionTypeAntiReplayMaxSize
|
||||
OptionTypeAntiReplayEvictionTime
|
||||
|
||||
OptionTypeSecret
|
||||
OptionTypeAdtag
|
||||
)
|
||||
|
||||
type BufferSize struct {
|
||||
Read int `json:"read"`
|
||||
Write int `json:"write"`
|
||||
}
|
||||
|
||||
type AntiReplay struct {
|
||||
MaxSize int `json:"max_size"`
|
||||
EvictionTime time.Duration `json:"duration"`
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
Prefix string `json:"prefix"`
|
||||
}
|
||||
|
||||
type StatsdStats struct {
|
||||
Stats
|
||||
|
||||
Addr Addr `json:"addr"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
TagsFormat statsd.TagFormat `json:"format"`
|
||||
}
|
||||
|
||||
type PrometheusStats struct {
|
||||
Stats
|
||||
}
|
||||
|
||||
type Addr struct {
|
||||
IP net.IP `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
net string
|
||||
}
|
||||
|
||||
func (a Addr) Network() string {
|
||||
if a.net == "" {
|
||||
return "tcp"
|
||||
}
|
||||
return a.net
|
||||
}
|
||||
|
||||
func (a Addr) String() string {
|
||||
return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port))
|
||||
}
|
||||
|
||||
func (a Addr) MarshalJSON() ([]byte, error) {
|
||||
data := map[string]string{
|
||||
"network": a.Network(),
|
||||
"addr": a.String(),
|
||||
}
|
||||
return json.Marshal(data)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Debug bool
|
||||
Verbose bool
|
||||
SecureMode bool
|
||||
SecureOnly bool
|
||||
BufferSize BufferSize `json:"buffer_size"`
|
||||
AntiReplay AntiReplay `json:"anti_replay"`
|
||||
|
||||
ReadBufferSize int
|
||||
WriteBufferSize int
|
||||
ListenAddr Addr `json:"listen_addr"`
|
||||
PublicIPv4Addr Addr `json:"public_ipv4_addr"`
|
||||
PublicIPv6Addr Addr `json:"public_ipv6_addr"`
|
||||
StatsAddr Addr `json:"stats_addr"`
|
||||
|
||||
BindPort uint16
|
||||
PublicIPv4Port uint16
|
||||
PublicIPv6Port uint16
|
||||
StatsPort uint16
|
||||
StatsdStats StatsdStats `json:"stats_statsd"`
|
||||
PrometheusStats PrometheusStats `json:"stats_prometheus"`
|
||||
|
||||
BindIP net.IP
|
||||
PublicIPv4 net.IP
|
||||
PublicIPv6 net.IP
|
||||
StatsIP net.IP
|
||||
|
||||
AntiReplayMaxSize int
|
||||
AntiReplayEvictionTime time.Duration
|
||||
|
||||
StatsD struct {
|
||||
Addr net.Addr
|
||||
Prefix string
|
||||
Tags map[string]string
|
||||
TagsFormat statsd.TagFormat
|
||||
Enabled bool
|
||||
}
|
||||
Prometheus struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
Secret []byte
|
||||
AdTag []byte
|
||||
Debug bool `json:"debug"`
|
||||
Verbose bool `json:"verbose"`
|
||||
SecretMode SecretMode `json:"secret_mode"`
|
||||
Secret []byte `json:"secret"`
|
||||
AdTag []byte `json:"adtag"`
|
||||
}
|
||||
|
||||
// URLs contains links to the proxy (tg://, t.me) and their QR codes.
|
||||
type URLs struct {
|
||||
TG string `json:"tg_url"`
|
||||
TMe string `json:"tme_url"`
|
||||
TGQRCode string `json:"tg_qrcode"`
|
||||
TMeQRCode string `json:"tme_qrcode"`
|
||||
func (c Config) String() string {
|
||||
data, _ := json.Marshal(c)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// IPURLs contains links to both ipv4 and ipv6 of the proxy.
|
||||
type IPURLs struct {
|
||||
IPv4 URLs `json:"ipv4"`
|
||||
IPv6 URLs `json:"ipv6"`
|
||||
BotSecret string `json:"secret_for_mtproxybot"`
|
||||
type ConfigOpt struct {
|
||||
Option OptionType
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// BindAddr returns connection for this server to bind to.
|
||||
func (c *Config) BindAddr() string {
|
||||
return getAddr(c.BindIP, c.BindPort)
|
||||
}
|
||||
var C = Config{}
|
||||
|
||||
// StatAddr returns connection string to the stats API.
|
||||
func (c *Config) StatAddr() string {
|
||||
return getAddr(c.StatsIP, c.StatsPort)
|
||||
}
|
||||
|
||||
// UseMiddleProxy defines if this proxy has to connect middle proxies
|
||||
// which supports promoted channels or directly access Telegram.
|
||||
func (c *Config) UseMiddleProxy() bool {
|
||||
return len(c.AdTag) > 0
|
||||
}
|
||||
|
||||
// BotSecretString returns secret string which should work with MTProxybot.
|
||||
func (c *Config) BotSecretString() string {
|
||||
return hex.EncodeToString(c.Secret)
|
||||
}
|
||||
|
||||
// SecretString returns a secret in a form entered on the start of the
|
||||
// application.
|
||||
func (c *Config) SecretString() string {
|
||||
secret := c.BotSecretString()
|
||||
if c.SecureMode {
|
||||
return "dd" + secret
|
||||
}
|
||||
return secret
|
||||
}
|
||||
|
||||
// GetURLs returns configured IPURLs instance with links to this server.
|
||||
func (c *Config) GetURLs() IPURLs {
|
||||
urls := IPURLs{}
|
||||
secret := c.SecretString()
|
||||
if c.PublicIPv4 != nil {
|
||||
urls.IPv4 = getURLs(c.PublicIPv4, c.PublicIPv4Port, secret)
|
||||
}
|
||||
if c.PublicIPv6 != nil {
|
||||
urls.IPv6 = getURLs(c.PublicIPv6, c.PublicIPv6Port, secret)
|
||||
}
|
||||
urls.BotSecret = c.BotSecretString()
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
func getAddr(host fmt.Stringer, port uint16) string {
|
||||
return net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
|
||||
}
|
||||
|
||||
// NewConfig returns new configuration. If required, it manages and
|
||||
// fetches data from external sources. Parameters passed to this
|
||||
// function, should come from command line arguments.
|
||||
func NewConfig(debug, verbose bool, // nolint: gocyclo
|
||||
writeBufferSize, readBufferSize uint32,
|
||||
bindIP, publicIPv4, publicIPv6, statsIP net.IP,
|
||||
bindPort, publicIPv4Port, publicIPv6Port, statsPort, statsdPort uint16,
|
||||
statsdIP, statsdNetwork, statsdPrefix, statsdTagsFormat string,
|
||||
statsdTags map[string]string, prometheusPrefix string,
|
||||
secureOnly bool,
|
||||
antiReplayMaxSize int, antiReplayEvictionTime time.Duration,
|
||||
secret, adtag []byte) (*Config, error) {
|
||||
secureMode := secureOnly
|
||||
if bytes.HasPrefix(secret, []byte{0xdd}) && len(secret) == 17 {
|
||||
secureMode = true
|
||||
secret = bytes.TrimPrefix(secret, []byte{0xdd})
|
||||
} else if len(secret) != 16 {
|
||||
return nil, errors.New("Telegram demands secret of length 32")
|
||||
}
|
||||
|
||||
var err error
|
||||
if publicIPv4 == nil {
|
||||
publicIPv4, err = getGlobalIPv4()
|
||||
if err != nil {
|
||||
publicIPv4 = nil
|
||||
} else if publicIPv4.To4() == nil {
|
||||
return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String())
|
||||
}
|
||||
}
|
||||
if publicIPv4Port == 0 {
|
||||
publicIPv4Port = bindPort
|
||||
}
|
||||
|
||||
if publicIPv6 == nil {
|
||||
publicIPv6, err = getGlobalIPv6()
|
||||
if err != nil {
|
||||
publicIPv6 = nil
|
||||
} else if publicIPv6.To4() != nil {
|
||||
return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String())
|
||||
}
|
||||
}
|
||||
if publicIPv6Port == 0 {
|
||||
publicIPv6Port = bindPort
|
||||
}
|
||||
|
||||
if statsIP == nil {
|
||||
statsIP = publicIPv4
|
||||
}
|
||||
|
||||
conf := &Config{
|
||||
Debug: debug,
|
||||
Verbose: verbose,
|
||||
SecureOnly: secureOnly,
|
||||
BindIP: bindIP,
|
||||
BindPort: bindPort,
|
||||
PublicIPv4: publicIPv4,
|
||||
PublicIPv4Port: publicIPv4Port,
|
||||
PublicIPv6: publicIPv6,
|
||||
PublicIPv6Port: publicIPv6Port,
|
||||
StatsIP: statsIP,
|
||||
StatsPort: statsPort,
|
||||
Secret: secret,
|
||||
AdTag: adtag,
|
||||
SecureMode: secureMode,
|
||||
ReadBufferSize: int(readBufferSize),
|
||||
WriteBufferSize: int(writeBufferSize),
|
||||
AntiReplayMaxSize: antiReplayMaxSize,
|
||||
AntiReplayEvictionTime: antiReplayEvictionTime,
|
||||
}
|
||||
conf.Prometheus.Prefix = prometheusPrefix
|
||||
|
||||
if statsdIP != "" {
|
||||
conf.StatsD.Enabled = true
|
||||
conf.StatsD.Prefix = statsdPrefix
|
||||
conf.StatsD.Tags = statsdTags
|
||||
|
||||
var (
|
||||
addr net.Addr
|
||||
err error
|
||||
)
|
||||
hostPort := net.JoinHostPort(statsdIP, strconv.Itoa(int(statsdPort)))
|
||||
switch statsdNetwork {
|
||||
case "tcp":
|
||||
addr, err = net.ResolveTCPAddr("tcp", hostPort)
|
||||
case "udp":
|
||||
addr, err = net.ResolveUDPAddr("udp", hostPort)
|
||||
func Init(options ...ConfigOpt) error { // nolint: gocyclo
|
||||
for _, opt := range options {
|
||||
switch opt.Option {
|
||||
case OptionTypeDebug:
|
||||
C.Debug = opt.Value.(bool)
|
||||
case OptionTypeVerbose:
|
||||
C.Verbose = opt.Value.(bool)
|
||||
case OptionTypeBindIP:
|
||||
C.ListenAddr.IP = opt.Value.(net.IP)
|
||||
case OptionTypeBindPort:
|
||||
C.ListenAddr.Port = int(opt.Value.(uint16))
|
||||
case OptionTypePublicIPv4:
|
||||
C.PublicIPv4Addr.IP = opt.Value.(net.IP)
|
||||
case OptionTypePublicIPv4Port:
|
||||
C.PublicIPv4Addr.Port = int(opt.Value.(uint16))
|
||||
case OptionTypePublicIPv6:
|
||||
C.PublicIPv6Addr.IP = opt.Value.(net.IP)
|
||||
case OptionTypePublicIPv6Port:
|
||||
C.PublicIPv6Addr.Port = int(opt.Value.(uint16))
|
||||
case OptionTypeStatsIP:
|
||||
C.StatsAddr.IP = opt.Value.(net.IP)
|
||||
case OptionTypeStatsPort:
|
||||
C.StatsAddr.Port = int(opt.Value.(uint16))
|
||||
case OptionTypeStatsdIP:
|
||||
C.StatsdStats.Addr.IP = opt.Value.(net.IP)
|
||||
case OptionTypeStatsdPort:
|
||||
C.StatsdStats.Addr.Port = int(opt.Value.(uint16))
|
||||
case OptionTypeStatsdNetwork:
|
||||
C.StatsdStats.Addr.net = opt.Value.(string)
|
||||
case OptionTypeStatsdPrefix:
|
||||
C.StatsdStats.Prefix = opt.Value.(string)
|
||||
case OptionTypeStatsdTagsFormat:
|
||||
value := opt.Value.(string)
|
||||
switch value {
|
||||
case "datadog":
|
||||
C.StatsdStats.TagsFormat = statsd.Datadog
|
||||
case "influxdb":
|
||||
C.StatsdStats.TagsFormat = statsd.InfluxDB
|
||||
default:
|
||||
return errors.Errorf("Incorrect statsd tag %s", value)
|
||||
}
|
||||
case OptionTypeStatsdTags:
|
||||
C.StatsdStats.Tags = opt.Value.(map[string]string)
|
||||
case OptionTypePrometheusPrefix:
|
||||
C.PrometheusStats.Prefix = opt.Value.(string)
|
||||
case OptionTypeWriteBufferSize:
|
||||
C.BufferSize.Write = int(opt.Value.(uint32))
|
||||
case OptionTypeReadBufferSize:
|
||||
C.BufferSize.Read = int(opt.Value.(uint32))
|
||||
case OptionTypeAntiReplayMaxSize:
|
||||
C.AntiReplay.MaxSize = opt.Value.(int)
|
||||
case OptionTypeAntiReplayEvictionTime:
|
||||
C.AntiReplay.EvictionTime = opt.Value.(time.Duration)
|
||||
case OptionTypeSecret:
|
||||
C.Secret = opt.Value.([]byte)
|
||||
case OptionTypeAdtag:
|
||||
C.AdTag = opt.Value.([]byte)
|
||||
default:
|
||||
err = errors.Errorf("Unknown network %s", statsdNetwork)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Annotate(err, "Cannot resolve statsd address")
|
||||
}
|
||||
conf.StatsD.Addr = addr
|
||||
|
||||
switch statsdTagsFormat {
|
||||
case "datadog":
|
||||
conf.StatsD.TagsFormat = statsd.Datadog
|
||||
case "influxdb":
|
||||
conf.StatsD.TagsFormat = statsd.InfluxDB
|
||||
case "":
|
||||
default:
|
||||
return nil, errors.Errorf("Unknown tags format %s", statsdTagsFormat)
|
||||
return errors.Errorf("Unknown tag %v", opt.Option)
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
switch {
|
||||
case len(C.Secret) == 1+SimpleSecretLength && bytes.HasPrefix(C.Secret, []byte{0xdd}):
|
||||
C.SecretMode = SecretModeSecured
|
||||
C.Secret = bytes.TrimPrefix(C.Secret, []byte{0xdd})
|
||||
case len(C.Secret) == SimpleSecretLength:
|
||||
C.SecretMode = SecretModeSimple
|
||||
default:
|
||||
return errors.New("Incorrect secret")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitPublicAddress() error {
|
||||
if C.PublicIPv4Addr.Port == 0 {
|
||||
C.PublicIPv4Addr.Port = C.ListenAddr.Port
|
||||
}
|
||||
if C.PublicIPv6Addr.Port == 0 {
|
||||
C.PublicIPv6Addr.Port = C.ListenAddr.Port
|
||||
}
|
||||
|
||||
foundAddress := C.PublicIPv4Addr.IP != nil || C.PublicIPv6Addr.IP != nil
|
||||
if C.PublicIPv4Addr.IP == nil {
|
||||
ip, err := getGlobalIPv4()
|
||||
if err != nil {
|
||||
zap.S().Warnw("Cannot resolve public address", "error", err)
|
||||
} else {
|
||||
C.PublicIPv4Addr.IP = ip
|
||||
foundAddress = true
|
||||
}
|
||||
}
|
||||
if C.PublicIPv6Addr.IP == nil {
|
||||
ip, err := getGlobalIPv6()
|
||||
if err != nil {
|
||||
zap.S().Warnw("Cannot resolve public address", "error", err)
|
||||
} else {
|
||||
C.PublicIPv6Addr.IP = ip
|
||||
foundAddress = true
|
||||
}
|
||||
}
|
||||
|
||||
if !foundAddress {
|
||||
return errors.New("Cannot resolve any public address")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+23
-6
@@ -2,28 +2,42 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
const ifconfigAddress = "https://ifconfig.co/ip"
|
||||
const (
|
||||
ifconfigAddress = "https://ifconfig.co/ip"
|
||||
ifconfigTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func getGlobalIPv4() (net.IP, error) {
|
||||
return fetchIP("tcp4")
|
||||
ip, err := fetchIP("tcp4")
|
||||
if err != nil || ip.To4() == nil {
|
||||
return nil, errors.Annotate(err, "Cannot find public ipv4 address")
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func getGlobalIPv6() (net.IP, error) {
|
||||
return fetchIP("tcp6")
|
||||
ip, err := fetchIP("tcp6")
|
||||
if err != nil || ip.To4() != nil {
|
||||
return nil, errors.Annotate(err, "Cannot find public ipv6 address")
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func fetchIP(network string) (net.IP, error) {
|
||||
dialer := &net.Dialer{FallbackDelay: -1}
|
||||
client := &http.Client{
|
||||
Jar: nil,
|
||||
Jar: nil,
|
||||
Timeout: ifconfigTimeout,
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
@@ -33,13 +47,16 @@ func fetchIP(network string) (net.IP, error) {
|
||||
|
||||
resp, err := client.Get(ifconfigAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if resp != nil {
|
||||
io.Copy(ioutil.Discard, resp.Body) // nolint: errcheck
|
||||
}
|
||||
return nil, errors.Annotate(err, "Cannot perform a request")
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
|
||||
respDataBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Annotate(err, "Cannot read response body")
|
||||
}
|
||||
respData := strings.TrimSpace(string(respDataBytes))
|
||||
|
||||
|
||||
+32
-5
@@ -1,15 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"net"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func getURLs(addr net.IP, port uint16, secret string) (urls URLs) {
|
||||
type URLs struct {
|
||||
TG string `json:"tg_url"`
|
||||
TMe string `json:"tme_url"`
|
||||
TGQRCode string `json:"tg_qrcode"`
|
||||
TMeQRCode string `json:"tme_qrcode"`
|
||||
}
|
||||
|
||||
type IPURLs struct {
|
||||
IPv4 URLs `json:"ipv4"`
|
||||
IPv6 URLs `json:"ipv6"`
|
||||
BotSecret string `json:"secret_for_mtproxybot"`
|
||||
}
|
||||
|
||||
func GetURLs() (urls IPURLs) {
|
||||
secret := ""
|
||||
switch C.SecretMode {
|
||||
case SecretModeSimple:
|
||||
secret = hex.EncodeToString(C.Secret)
|
||||
case SecretModeSecured:
|
||||
secret = "dd" + hex.EncodeToString(C.Secret)
|
||||
}
|
||||
|
||||
urls.IPv4 = makeURLs(&C.PublicIPv4Addr, secret)
|
||||
urls.IPv6 = makeURLs(&C.PublicIPv6Addr, secret)
|
||||
urls.BotSecret = secret
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
func makeURLs(addr *Addr, secret string) (urls URLs) {
|
||||
values := url.Values{}
|
||||
values.Set("server", addr.String())
|
||||
values.Set("port", strconv.Itoa(int(port)))
|
||||
values.Set("address", addr.String())
|
||||
values.Set("secret", secret)
|
||||
|
||||
urls.TG = makeTGURL(values)
|
||||
|
||||
Reference in New Issue
Block a user