Fixes for obfuscated2

This commit is contained in:
9seconds
2021-03-18 17:15:55 +03:00
parent 0ad2d61742
commit 0330a0e5cd
14 changed files with 335 additions and 19 deletions
+6
View File
@@ -0,0 +1,6 @@
package antireplay
const (
DefaultMaxSize = 10 * 1024 * 1024 // 10mib
DefaultErrorRate = 0.0001
)
+1
View File
@@ -5,5 +5,6 @@ import "github.com/alecthomas/kong"
type CLI struct {
GenerateSecret GenerateSecret `kong:"cmd,help='Generate new proxy secret'"`
Access Access `kong:"cmd,help='Print access information.'"`
Run Proxy `kong:"cmd,help='Run proxy.'"`
Version kong.VersionFlag `kong:"help='Print version.',short='v'"`
}
+205
View File
@@ -0,0 +1,205 @@
package cli
import (
"fmt"
"net"
"os"
"github.com/9seconds/mtg/v2/antireplay"
"github.com/9seconds/mtg/v2/events"
"github.com/9seconds/mtg/v2/ipblocklist"
"github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/stats"
"github.com/9seconds/mtg/v2/timeattack"
"github.com/9seconds/mtg/v2/utils"
"github.com/rs/zerolog"
)
type Proxy struct {
base
prometheusListener net.Listener
prometheus *stats.PrometheusFactory
statsdFactory *stats.StatsdFactory
}
func (c *Proxy) Run(cli *CLI, version string) error {
if err := c.ReadConfig(version); err != nil {
return fmt.Errorf("cannot init config: %w", err)
}
return c.Execute()
}
func (c *Proxy) Execute() error { // nolint: funlen
zerolog.TimeFieldFormat = zerolog.TimeFormatUnixMs
zerolog.TimestampFieldName = "timestamp"
zerolog.LevelFieldName = "level"
ctx := utils.RootContext()
opts := mtglib.ProxyOpts{
Logger: logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
Network: c.Network,
AntiReplayCache: antireplay.NewNoop(),
IPBlocklist: ipblocklist.NewNoop(),
TimeAttackDetector: timeattack.NewNoop(),
EventStream: events.NewNoopStream(),
Secret: c.Config.Secret,
BufferSize: c.Config.TCPBuffer.Value(mtglib.DefaultBufferSize),
CloakPort: c.Config.CloakPort.Value(mtglib.DefaultCloakPort),
IdleTimeout: c.Config.Network.Timeout.Idle.Value(mtglib.DefaultIdleTimeout),
PreferIP: c.Config.PreferIP.Value(mtglib.DefaultPreferIP),
}
defer func() {
opts.AntiReplayCache.Shutdown()
opts.IPBlocklist.Shutdown()
opts.EventStream.Shutdown()
}()
if opts.Concurrency == 0 {
opts.Concurrency = mtglib.DefaultConcurrency
}
opts.Logger.BindStr("configuration", c.Config.String()).Debug("configuration")
c.setupAntiReplayCache(&opts)
c.setupTimeAttackDetector(&opts)
if err := c.setupIPBlocklist(&opts); err != nil {
return fmt.Errorf("cannot setup ipblocklist: %w", err)
}
if err := c.setupEventStream(&opts); err != nil {
return fmt.Errorf("cannot setup event stream: %w", err)
}
proxy, err := mtglib.NewProxy(opts)
if err != nil {
return fmt.Errorf("cannot create a proxy: %w", err)
}
listener, err := net.Listen("tcp", c.Config.BindTo.String())
if err != nil {
return fmt.Errorf("cannot start proxy: %w", err)
}
go proxy.Serve(listener) // nolint: errcheck
<-ctx.Done()
listener.Close()
if c.prometheusListener != nil {
c.prometheusListener.Close()
}
if c.prometheus != nil {
c.prometheus.Close()
}
if c.statsdFactory != nil {
c.statsdFactory.Close()
}
return nil
}
func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) {
if !c.Config.Defense.AntiReplay.Enabled {
return
}
opts.AntiReplayCache = antireplay.NewStableBloomFilter(
c.Config.Defense.AntiReplay.MaxSize.Value(antireplay.DefaultMaxSize),
c.Config.Defense.AntiReplay.ErrorRate.Value(antireplay.DefaultErrorRate),
)
}
func (c *Proxy) setupTimeAttackDetector(opts *mtglib.ProxyOpts) {
if !c.Config.Defense.Time.Enabled {
return
}
opts.TimeAttackDetector = timeattack.NewDetector(
c.Config.Defense.Time.AllowSkewness.Value(timeattack.DefaultDuration),
)
}
func (c *Proxy) setupIPBlocklist(opts *mtglib.ProxyOpts) error {
if !c.Config.Defense.Blocklist.Enabled {
return nil
}
remoteURLs := []string{}
localFiles := []string{}
for _, v := range c.Config.Defense.Blocklist.URLs {
if v.IsRemote() {
remoteURLs = append(remoteURLs, v.String())
} else {
localFiles = append(localFiles, v.String())
}
}
firehol, err := ipblocklist.NewFirehol(opts.Logger.Named("ipblockist"),
c.Network,
c.Config.Defense.Blocklist.DownloadConcurrency,
remoteURLs,
localFiles)
if err != nil {
return err // nolint: wrapcheck
}
go firehol.Run(c.Config.Defense.Blocklist.UpdateEach.Value(ipblocklist.DefaultUpdateEach))
opts.IPBlocklist = firehol
return nil
}
func (c *Proxy) setupEventStream(opts *mtglib.ProxyOpts) error {
factories := make([]events.ObserverFactory, 0, 2)
if c.Config.Stats.StatsD.Enabled {
statsdFactory, err := stats.NewStatsd(
c.Config.Stats.StatsD.Address.String(),
opts.Logger.Named("statsd"),
c.Config.Stats.StatsD.MetricPrefix.Value(stats.DefaultStatsdMetricPrefix),
c.Config.Stats.StatsD.TagFormat.Value(stats.DefaultStatsdTagFormat))
if err != nil {
return fmt.Errorf("cannot build statsd observer: %w", err)
}
c.statsdFactory = &statsdFactory
factories = append(factories, statsdFactory.Make)
}
if c.Config.Stats.Prometheus.Enabled {
prometheus := stats.NewPrometheus(
c.Config.Stats.Prometheus.MetricPrefix.Value(stats.DefaultMetricPrefix),
c.Config.Stats.Prometheus.HTTPPath.Value("/"),
)
listener, err := net.Listen("tcp", c.Config.Stats.Prometheus.BindTo.String())
if err != nil {
return fmt.Errorf("cannot start a listener for prometheus: %w", err)
}
go prometheus.Serve(listener) // nolint: errcheck
c.prometheusListener = listener
c.prometheus = prometheus
factories = append(factories, prometheus.Make)
}
if len(factories) > 0 {
opts.EventStream = events.NewEventStream(factories)
}
return nil
}
+1 -1
View File
@@ -323,7 +323,7 @@ func NewFirehol(logger mtglib.Logger, network mtglib.Network,
}
if downloadConcurrency == 0 {
downloadConcurrency = 1
downloadConcurrency = DefaultDownloadConcurrency
}
workerPool, _ := ants.NewPool(int(downloadConcurrency))
+8
View File
@@ -0,0 +1,8 @@
package ipblocklist
import "time"
const (
DefaultDownloadConcurrency = 1
DefaultUpdateEach = 12 * time.Hour
)
+5
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/9seconds/mtg/v2/cli"
"github.com/9seconds/mtg/v2/utils"
"github.com/alecthomas/kong"
)
@@ -13,6 +14,10 @@ var version = "dev" // has to be set by ldflags
func main() {
rand.Seed(time.Now().UTC().UnixNano())
if err := utils.SetLimits(); err != nil {
panic(err)
}
cli := &cli.CLI{}
ctx := kong.Parse(cli, kong.Vars{
"version": version,
@@ -9,7 +9,7 @@ import (
)
// Connection Type secure. We support only fake tls.
var clientHandshakeMagic = []byte{0xdd, 0xdd, 0xdd, 0xdd}
var clientHandshakeConnectionType = []byte{0xdd, 0xdd, 0xdd, 0xdd}
func ClientHandshake(secret []byte, reader io.Reader) (int16, cipher.Stream, cipher.Stream, error) {
handshakeFrame := acquireHandshakeFrame()
@@ -42,8 +42,8 @@ func ClientHandshake(secret []byte, reader io.Reader) (int16, cipher.Stream, cip
decryptor.XORKeyStream(handshakeFrame.data[:], handshakeFrame.data[:])
if magic := handshakeFrame.magic(); subtle.ConstantTimeCompare(clientHandshakeMagic, magic) != 1 {
return 0, nil, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(magic))
if val := handshakeFrame.connectionType(); subtle.ConstantTimeCompare(clientHandshakeConnectionType, val) != 1 {
return 0, nil, nil, fmt.Errorf("unsupported connection type: %s", hex.EncodeToString(val))
}
return handshakeFrame.dc(), encryptor, decryptor, nil
+15 -15
View File
@@ -5,17 +5,17 @@ import "encoding/binary"
const (
handshakeFrameLen = 64
handshakeFrameLenKey = 32
handshakeFrameLenIV = 16
handshakeFrameLenMagic = 4
handshakeFrameLenDC = 2
handshakeFrameLenKey = 32
handshakeFrameLenIV = 16
handshakeFrameLenConnectionType = 4
handshakeFrameLenDC = 2
handshakeFrameOffsetStart = 8
handshakeFrameOffsetKey = handshakeFrameOffsetStart
handshakeFrameOffsetIV = handshakeFrameOffsetKey + handshakeFrameLenKey
handshakeFrameOffsetMagic = handshakeFrameOffsetIV + handshakeFrameLenIV
handshakeFrameOffsetDC = handshakeFrameOffsetMagic + handshakeFrameLenMagic
handshakeFrameOffsetEnd = handshakeFrameOffsetDC + handshakeFrameLenDC
handshakeFrameOffsetStart = 8
handshakeFrameOffsetKey = handshakeFrameOffsetStart
handshakeFrameOffsetIV = handshakeFrameOffsetKey + handshakeFrameLenKey
handshakeFrameOffsetConnectionType = handshakeFrameOffsetIV + handshakeFrameLenIV
handshakeFrameOffsetDC = handshakeFrameOffsetConnectionType + handshakeFrameLenConnectionType
handshakeFrameOffsetEnd = handshakeFrameOffsetDC + handshakeFrameLenDC
)
// A structure of obfuscated2 handshake frame is following:
@@ -25,7 +25,7 @@ const (
// - 8 bytes of noise
// - 32 bytes of AES Key
// - 16 bytes of AES IV
// - 4 bytes of 'magic' - this has some settings like a connection type
// - 4 bytes of 'connection type' - this has some setting like a connection type
// - 2 bytes of 'DC'. DC is little endian int16
// - 2 bytes of noise
type handshakeFrame struct {
@@ -39,13 +39,13 @@ func (h *handshakeFrame) dc() int16 {
}
func (h *handshakeFrame) key() []byte {
return h.data[handshakeFrameLenKey:handshakeFrameOffsetIV]
return h.data[handshakeFrameOffsetKey:handshakeFrameOffsetIV]
}
func (h *handshakeFrame) iv() []byte {
return h.data[handshakeFrameOffsetIV:handshakeFrameOffsetMagic]
return h.data[handshakeFrameOffsetIV:handshakeFrameOffsetConnectionType]
}
func (h *handshakeFrame) magic() []byte {
return h.data[handshakeFrameOffsetMagic:handshakeFrameOffsetDC]
func (h *handshakeFrame) connectionType() []byte {
return h.data[handshakeFrameOffsetConnectionType:handshakeFrameOffsetDC]
}
+5
View File
@@ -1,6 +1,11 @@
package stats
const (
DefaultMetricPrefix = "mtg"
DefaultStatsdMetricPrefix = DefaultMetricPrefix + "."
DefaultStatsdTagFormat = "datadog"
MetricActiveConnection = "active_connections"
MetricSessionDuration = "session_duration"
MetricConcurrencyLimited = "concurrency_limited"
+7
View File
@@ -0,0 +1,7 @@
package timeattack
import "time"
const (
DefaultDuration = time.Second
)
+24
View File
@@ -0,0 +1,24 @@
// +build !windows
package utils
import (
"fmt"
"golang.org/x/sys/unix"
)
func SetLimits() error {
rLimit := unix.Rlimit{}
if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
return fmt.Errorf("cannot get rlimit: %w", err)
}
rLimit.Cur = rLimit.Max
if err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit); err != nil {
return fmt.Errorf("cannot set rlimit: %w", err)
}
return nil
}
+7
View File
@@ -0,0 +1,7 @@
// +build windows
package utils
func SetLimits() error {
return nil
}
+25
View File
@@ -0,0 +1,25 @@
// +build !windows
package utils
import (
"context"
"os"
"os/signal"
"syscall"
)
func RootContext() context.Context {
ctx, cancel := context.WithCancel(context.Background())
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
for range sigChan {
cancel()
}
}()
return ctx
}
+23
View File
@@ -0,0 +1,23 @@
// +build windows
package utils
import (
"context"
"os"
"os/signal"
)
func RootContext() context.Context {
ctx, cancel := context.WithCancel(context.Background())
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt)
go func() {
for range sigChan {
cancel()
}
}()
return ctx
}