mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 15:24:01 +03:00
Add base dialers module
This commit is contained in:
@@ -44,6 +44,13 @@ prefer-ips = "prefer-ipv6"
|
||||
# access.
|
||||
cloak-port = 443
|
||||
|
||||
# Path to access file. Each time when proxy starts up, it writes an
|
||||
# access file. This file contains a JSON with settings how to access
|
||||
# this proxy.
|
||||
#
|
||||
# Pass filepath here or '-' if you want to dump into stdout.
|
||||
access-file = "-"
|
||||
|
||||
# FakeTLS can compare timestamps to prevent probes. Each message has
|
||||
# encrypted timestamp. So, mtg can compare this timestamp and decide if
|
||||
# we need to proceed with connection or not.
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
module github.com/9seconds/mtg/v2
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6
|
||||
github.com/libp2p/go-reuseport v0.0.2
|
||||
github.com/pelletier/go-toml v1.8.1
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.4
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
|
||||
)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
|
||||
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU=
|
||||
github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ=
|
||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.4 h1:4VzajPL7RwwmImysBSvI+lm/UaegDGQq3hr42dYo3gs=
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.4/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib/dialers"
|
||||
)
|
||||
|
||||
var version = "dev" // has to be set by ldflags
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
f, _ := os.Open("example.config.toml")
|
||||
|
||||
fmt.Println(parseRawConfig(f))
|
||||
|
||||
bd, _ := dialers.NewDefaultBaseDialer(0, 0)
|
||||
d, _ := dialers.MakeDialer(bd, "9.9.9.9", 0)
|
||||
|
||||
r, err := d.HTTP.Get("https://ifconfig.co")
|
||||
|
||||
fmt.Println(err)
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package dialers
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
DefaultTimeout = 10 * time.Second
|
||||
DefaultHTTPTimeout = DefaultTimeout
|
||||
DefaultBufferSize = 4096
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-reuseport"
|
||||
)
|
||||
|
||||
type defaultBaseDialer struct {
|
||||
net.Dialer
|
||||
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func (d *defaultBaseDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func (d *defaultBaseDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported network %s", network)
|
||||
}
|
||||
|
||||
conn, err := d.Dialer.DialContext(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot dial to %s: %w", address, err)
|
||||
}
|
||||
|
||||
tcpConn := conn.(*net.TCPConn)
|
||||
|
||||
if err := tcpConn.SetNoDelay(true); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("cannot set TCP_NO_DELAY: %w", err)
|
||||
}
|
||||
|
||||
if err := tcpConn.SetReadBuffer(d.bufferSize); err != nil {
|
||||
tcpConn.Close()
|
||||
return nil, fmt.Errorf("cannot set read buffer size: %w", err)
|
||||
}
|
||||
|
||||
if err := tcpConn.SetWriteBuffer(d.bufferSize); err != nil {
|
||||
tcpConn.Close()
|
||||
return nil, fmt.Errorf("cannot set write buffer size: %w", err)
|
||||
}
|
||||
|
||||
if err := tcpConn.SetKeepAlive(true); err != nil {
|
||||
tcpConn.Close()
|
||||
return nil, fmt.Errorf("cannot enable keep-alive: %w", err)
|
||||
}
|
||||
|
||||
return tcpConn, nil
|
||||
}
|
||||
|
||||
func NewDefaultBaseDialer(timeout time.Duration, bufferSize int) (BaseDialer, error) {
|
||||
switch {
|
||||
case timeout < 0:
|
||||
return nil, fmt.Errorf("timeout %v should be positive number", timeout)
|
||||
case bufferSize < 0:
|
||||
return nil, fmt.Errorf("buffer size %s should be positive number", bufferSize)
|
||||
}
|
||||
|
||||
if timeout == 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
|
||||
if bufferSize == 0 {
|
||||
bufferSize = DefaultBufferSize
|
||||
}
|
||||
|
||||
return &defaultBaseDialer{
|
||||
Dialer: net.Dialer{
|
||||
Timeout: timeout,
|
||||
Control: reuseport.Control,
|
||||
},
|
||||
bufferSize: bufferSize,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
doh "github.com/babolivier/go-doh-client"
|
||||
)
|
||||
|
||||
type Dialer struct {
|
||||
HTTP http.Client
|
||||
DNS doh.Resolver
|
||||
|
||||
baseDialer BaseDialer
|
||||
}
|
||||
|
||||
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
host, port, _ := net.SplitHostPort(address)
|
||||
|
||||
ips, err := d.resolveIPs(network, host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot resolve dns names: %w", err)
|
||||
}
|
||||
|
||||
rand.Shuffle(len(ips), func(i, j int) {
|
||||
ips[i], ips[j] = ips[j], ips[i]
|
||||
})
|
||||
|
||||
for _, v := range ips {
|
||||
if conn, err := d.baseDialer.DialContext(ctx, network, net.JoinHostPort(v, port)); err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot dial to %s:%s", network, address)
|
||||
}
|
||||
|
||||
func (d *Dialer) resolveIPs(network, address string) ([]string, error) {
|
||||
if net.ParseIP(address) != nil {
|
||||
return []string{address}, nil
|
||||
}
|
||||
|
||||
var ips []string
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4":
|
||||
if recs, _, err := d.DNS.LookupA(address); err == nil {
|
||||
for _, v := range recs {
|
||||
ips = append(ips, v.IP4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp6":
|
||||
if recs, _, err := d.DNS.LookupAAAA(address); err == nil {
|
||||
for _, v := range recs {
|
||||
ips = append(ips, v.IP6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("cannot find any ips for %s:%s", network, address)
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func MakeDialer(base BaseDialer, dohHostname string, httpTimeout time.Duration) (*Dialer, error) {
|
||||
switch {
|
||||
case httpTimeout < 0:
|
||||
return nil, fmt.Errorf("timeout should be positive number %v", httpTimeout)
|
||||
case httpTimeout == 0:
|
||||
httpTimeout = DefaultHTTPTimeout
|
||||
}
|
||||
|
||||
if net.ParseIP(dohHostname) == nil {
|
||||
return nil, fmt.Errorf("hostname %s should be IP address", dohHostname)
|
||||
}
|
||||
|
||||
dohHTTPClient := &http.Client{
|
||||
Timeout: httpTimeout,
|
||||
Transport: &http.Transport{
|
||||
DialContext: base.DialContext,
|
||||
},
|
||||
}
|
||||
rv := &Dialer{
|
||||
baseDialer: base,
|
||||
DNS: doh.Resolver{
|
||||
Host: dohHostname,
|
||||
Class: doh.IN,
|
||||
HTTPClient: dohHTTPClient,
|
||||
},
|
||||
}
|
||||
rv.HTTP = http.Client{
|
||||
Timeout: httpTimeout,
|
||||
Transport: &http.Transport{
|
||||
DialContext: rv.DialContext,
|
||||
},
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type BaseDialer interface {
|
||||
Dial(network, address string) (net.Conn, error)
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
shadowsocks "github.com/shadowsocks/go-shadowsocks2/core"
|
||||
)
|
||||
|
||||
type shadowsocksBaseDialer struct {
|
||||
base BaseDialer
|
||||
cipher shadowsocks.StreamConnCipher
|
||||
}
|
||||
|
||||
func (s *shadowsocksBaseDialer) Dial(network, address string) (net.Conn, error) {
|
||||
conn, err := s.base.Dial(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.cipher.StreamConn(conn), nil
|
||||
}
|
||||
|
||||
func (s *shadowsocksBaseDialer) DialContext(ctx context.Context,
|
||||
network, address string) (net.Conn, error) {
|
||||
conn, err := s.base.DialContext(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.cipher.StreamConn(conn), nil
|
||||
}
|
||||
|
||||
func NewShadowsocksBaseDialer(proxyUrl *url.URL,
|
||||
timeout time.Duration, bufferSize int) (BaseDialer, error) {
|
||||
username := proxyUrl.User.Username()
|
||||
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode payload: %w", err)
|
||||
}
|
||||
|
||||
chunks := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(chunks) != 2 {
|
||||
return nil, fmt.Errorf("incorrect payload %s", username)
|
||||
}
|
||||
|
||||
cipher, err := shadowsocks.PickCipher(chunks[0], nil, chunks[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize shadowsocks cipher: %w", err)
|
||||
}
|
||||
|
||||
baseDialer, err := NewDefaultBaseDialer(timeout, bufferSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize a base dialer: %w", err)
|
||||
|
||||
}
|
||||
|
||||
return &shadowsocksBaseDialer{
|
||||
base: baseDialer,
|
||||
cipher: cipher,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package dialers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
func NewSocks5BaseDialer(proxyUrl *url.URL, timeout time.Duration, bufferSize int) (BaseDialer, error) {
|
||||
baseDialer, err := NewDefaultBaseDialer(timeout, bufferSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize base dialer: %w", err)
|
||||
}
|
||||
|
||||
rv, err := proxy.FromURL(proxyUrl, baseDialer.(*defaultBaseDialer))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot initialize socks5 proxy dialer: %w", err)
|
||||
}
|
||||
|
||||
return rv.(BaseDialer), nil
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package mtglib
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Secret struct {
|
||||
Key []byte
|
||||
Host string
|
||||
}
|
||||
|
||||
func (s *Secret) MarshalText() ([]byte, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []byte(s.String()), nil
|
||||
}
|
||||
|
||||
func (s *Secret) UnmarshalText(text []byte) error {
|
||||
sc, err := ParseSecret(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = sc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Secret) Base64() string {
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (s Secret) EE() string {
|
||||
return "ee" + hex.EncodeToString(append(s.Key, s.Host...))
|
||||
}
|
||||
|
||||
func (s Secret) String() string {
|
||||
return base64.StdEncoding.EncodeToString(append(s.Key, s.Host...))
|
||||
}
|
||||
|
||||
func ParseSecret(secret string) (Secret, error) {
|
||||
rv := Secret{}
|
||||
|
||||
if secret == "" {
|
||||
return rv, errors.New("secret cannot be empty")
|
||||
}
|
||||
|
||||
decoded, err := base64.RawStdEncoding.DecodeString(secret)
|
||||
if err != nil && strings.HasPrefix(secret, "ee") {
|
||||
decoded, err = hex.DecodeString(strings.TrimPrefix(secret, "ee"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return rv, fmt.Errorf("incorrect secret format: %w", err)
|
||||
}
|
||||
|
||||
if len(decoded) < 33 {
|
||||
return rv, fmt.Errorf("secret %s has incorrect length", secret)
|
||||
}
|
||||
|
||||
rv.Key = decoded[:32]
|
||||
rv.Host = string(decoded[32:])
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pelletier/go-toml"
|
||||
)
|
||||
|
||||
type rawConfig struct {
|
||||
Debug bool `toml:"debug"`
|
||||
Secret string `toml:"secret"`
|
||||
BindTo string `toml:"bind-to"`
|
||||
TCPBuffer string `toml:"tcp-buffer"`
|
||||
PreferIP string `toml:"prefer-ip"`
|
||||
CloakPort uint `toml:"cloak-port"`
|
||||
AccessFile string `toml:"access-file"`
|
||||
Probes struct {
|
||||
Time struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
AllowSkewness string `toml:"allow-skewness"`
|
||||
} `toml:"time"`
|
||||
AntiReplay struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
MaxSize string `toml:"max-size"`
|
||||
TTL string `toml:"ttl"`
|
||||
} `toml:"anti-replay"`
|
||||
} `toml:"probes"`
|
||||
PublicIP struct {
|
||||
IPv4 string `toml:"ipv4"`
|
||||
IPv6 string `toml:"ipv6"`
|
||||
} `toml:"public-ip"`
|
||||
Dialers struct {
|
||||
Telegram string `toml:"telegram"`
|
||||
Default string `toml:"default"`
|
||||
} `toml:"dialers"`
|
||||
Stats struct {
|
||||
StatsD struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
Address string `toml:"address"`
|
||||
MetricPrefix string `toml:"metric-prefix"`
|
||||
} `toml:"statsd"`
|
||||
Prometheus struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
BindTo string `toml:"bind-to"`
|
||||
HttpPath string `toml:"http-path"`
|
||||
MetricPrefix string `toml:"metric-prefix"`
|
||||
} `toml:"prometheus"`
|
||||
} `toml:"stats"`
|
||||
}
|
||||
|
||||
func parseRawConfig(reader io.Reader) (*rawConfig, error) {
|
||||
conf := &rawConfig{}
|
||||
|
||||
if err := toml.NewDecoder(reader).Decode(conf); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse config: %w", err)
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
Reference in New Issue
Block a user