mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 12:44:02 +03:00
Remove timeattack module
This commit is contained in:
+8
-13
@@ -48,6 +48,14 @@ prefer-ip = "prefer-ipv6"
|
||||
# access.
|
||||
domain-fronting-port = 443
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Sometimes time can be skewed so we accept all messages within a
|
||||
# time range of this parameter.
|
||||
tolerate-time-skewness = "5s"
|
||||
|
||||
# network defines different network-related settings
|
||||
[network]
|
||||
# please be aware that mtg needs to do some external requests. For
|
||||
@@ -111,19 +119,6 @@ tcp = "5s"
|
||||
http = "10s"
|
||||
idle = "1m"
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Please ensure that you have some ntp active on this host. Otherwise,
|
||||
# you can endup with badly performing proxy.
|
||||
[defense.time]
|
||||
# You can enable/disable that. A good idea is always enable.
|
||||
enabled = true
|
||||
# Time can be skewed by many reasons. So, this is a time interval
|
||||
# when message is cosidered as a good one.
|
||||
allow-skewness = "5s"
|
||||
|
||||
# Some countries do active probing on Telegram connections. This technique
|
||||
# allows to protect from such effort.
|
||||
#
|
||||
|
||||
+5
-18
@@ -12,7 +12,6 @@ import (
|
||||
"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/rs/zerolog"
|
||||
)
|
||||
|
||||
@@ -41,12 +40,11 @@ func (c *Proxy) Execute() error {
|
||||
|
||||
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(),
|
||||
Logger: logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
|
||||
Network: c.Network,
|
||||
AntiReplayCache: antireplay.NewNoop(),
|
||||
IPBlocklist: ipblocklist.NewNoop(),
|
||||
EventStream: events.NewNoopStream(),
|
||||
|
||||
Secret: c.Config.Secret,
|
||||
BufferSize: c.Config.TCPBuffer.Value(mtglib.DefaultBufferSize),
|
||||
@@ -58,7 +56,6 @@ func (c *Proxy) Execute() error {
|
||||
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)
|
||||
@@ -98,16 +95,6 @@ func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) {
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
+18
-24
@@ -10,18 +10,15 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Debug bool `json:"debug"`
|
||||
Secret mtglib.Secret `json:"secret"`
|
||||
BindTo TypeHostPort `json:"bind-to"`
|
||||
TCPBuffer TypeBytes `json:"tcp-buffer"`
|
||||
PreferIP TypePreferIP `json:"prefer-ip"`
|
||||
DomainFrontingPort TypePort `json:"domain-fronting-port"`
|
||||
Concurrency uint `json:"concurrency"`
|
||||
Defense struct {
|
||||
Time struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AllowSkewness TypeDuration `json:"allow-skewness"`
|
||||
} `json:"time"`
|
||||
Debug bool `json:"debug"`
|
||||
Secret mtglib.Secret `json:"secret"`
|
||||
BindTo TypeHostPort `json:"bind-to"`
|
||||
TCPBuffer TypeBytes `json:"tcp-buffer"`
|
||||
PreferIP TypePreferIP `json:"prefer-ip"`
|
||||
DomainFrontingPort TypePort `json:"domain-fronting-port"`
|
||||
TolerateTimeSkewness TypeDuration `json:"tolerate-time-skewness"`
|
||||
Concurrency uint `json:"concurrency"`
|
||||
Defense struct {
|
||||
AntiReplay struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
MaxSize TypeBytes `json:"max-size"`
|
||||
@@ -85,18 +82,15 @@ func (c *Config) String() string {
|
||||
}
|
||||
|
||||
type configRaw struct {
|
||||
Debug bool `toml:"debug" json:"debug,omitempty"`
|
||||
Secret string `toml:"secret" json:"secret"`
|
||||
BindTo string `toml:"bind-to" json:"bind-to"`
|
||||
TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
|
||||
PreferIP string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
|
||||
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
|
||||
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
|
||||
Defense struct {
|
||||
Time struct {
|
||||
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
|
||||
AllowSkewness string `toml:"allow-skewness" json:"allow-skewness,omitempty"`
|
||||
} `toml:"time" json:"time,omitempty"`
|
||||
Debug bool `toml:"debug" json:"debug,omitempty"`
|
||||
Secret string `toml:"secret" json:"secret"`
|
||||
BindTo string `toml:"bind-to" json:"bind-to"`
|
||||
TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
|
||||
PreferIP string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
|
||||
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
|
||||
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerate-time-skewness,omitempty"`
|
||||
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
|
||||
Defense struct {
|
||||
AntiReplay struct {
|
||||
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
|
||||
MaxSize string `toml:"max-size" json:"max-size,omitempty"`
|
||||
|
||||
+4
-16
@@ -42,10 +42,6 @@ var (
|
||||
// create a proxy but anti replay cache value is undefined.
|
||||
ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined")
|
||||
|
||||
// ErrTimeAttackDetectorIsNotDefined is returned if you are trying to
|
||||
// create a proxy but time attack detector is not defined.
|
||||
ErrTimeAttackDetectorIsNotDefined = errors.New("time attack detector is not defined")
|
||||
|
||||
// ErrIPBlocklistIsNotDefined is returned if you are trying to
|
||||
// create a proxy but ip blocklist instance is not defined.
|
||||
ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
|
||||
@@ -75,6 +71,10 @@ const (
|
||||
// in case of idling.
|
||||
DefaultIdleTimeout = time.Minute
|
||||
|
||||
// DefaultTolerateTimeSkewness is a default timeout for time
|
||||
// skewness on a faketls timeout verification.
|
||||
DefaultTolerateTimeSkewness = 3 * time.Second
|
||||
|
||||
// DefaultPreferIP is a default value for Telegram IP connectivity
|
||||
// preference.
|
||||
DefaultPreferIP = "prefer-ipv6"
|
||||
@@ -206,18 +206,6 @@ type EventStream interface {
|
||||
Send(context.Context, Event)
|
||||
}
|
||||
|
||||
// TimeAttackDetector is an abstraction that checks a time, taken from
|
||||
// the faketls client hello message. This timestamp is encoded into
|
||||
// client-generated random bytes and can be extracted after some client
|
||||
// hello verification.
|
||||
//
|
||||
// This is mostly to prevent replay attacks.
|
||||
type TimeAttackDetector interface {
|
||||
// Valid returns an error if timestamp is invalid or should not be
|
||||
// accepted.
|
||||
Valid(time.Time) error
|
||||
}
|
||||
|
||||
// Logger defines an interface of the logger used by mtglib.
|
||||
//
|
||||
// Each logger has a name. It is possible to stack names to organize
|
||||
|
||||
@@ -19,6 +19,26 @@ type ClientHello struct {
|
||||
CipherSuite uint16
|
||||
}
|
||||
|
||||
func (c ClientHello) Valid(hostname string, tolerateTimeSkewness time.Duration) error {
|
||||
if c.Host != "" && c.Host != hostname {
|
||||
return fmt.Errorf("incorrect hostname %s", hostname)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
timeDiff := now.Sub(c.Time)
|
||||
if timeDiff < 0 {
|
||||
timeDiff = -timeDiff
|
||||
}
|
||||
|
||||
if timeDiff > tolerateTimeSkewness {
|
||||
return fmt.Errorf("incorrect timestamp. got=%d, now=%d, diff=%s",
|
||||
c.Time.Unix(), now.Unix(), timeDiff.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseClientHello(secret, handshake []byte) (ClientHello, error) {
|
||||
hello := ClientHello{}
|
||||
|
||||
|
||||
+30
-34
@@ -23,19 +23,19 @@ type Proxy struct {
|
||||
ctxCancel context.CancelFunc
|
||||
streamWaitGroup sync.WaitGroup
|
||||
|
||||
idleTimeout time.Duration
|
||||
bufferSize int
|
||||
domainFrontingPort int
|
||||
workerPool *ants.PoolWithFunc
|
||||
telegram *telegram.Telegram
|
||||
idleTimeout time.Duration
|
||||
tolerateTimeSkewness time.Duration
|
||||
bufferSize int
|
||||
domainFrontingPort int
|
||||
workerPool *ants.PoolWithFunc
|
||||
telegram *telegram.Telegram
|
||||
|
||||
secret Secret
|
||||
network Network
|
||||
antiReplayCache AntiReplayCache
|
||||
timeAttackDetector TimeAttackDetector
|
||||
ipBlocklist IPBlocklist
|
||||
eventStream EventStream
|
||||
logger Logger
|
||||
secret Secret
|
||||
network Network
|
||||
antiReplayCache AntiReplayCache
|
||||
ipBlocklist IPBlocklist
|
||||
eventStream EventStream
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// DomainFrontingAddress returns a host:port pair for a fronting domain.
|
||||
@@ -159,15 +159,11 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if hello.Host != "" && hello.Host != p.secret.Host {
|
||||
p.logger.BindStr("hostname", hello.Host).Info("incorrect domain was found in SNI")
|
||||
p.doDomainFronting(ctx, rewind)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if err := p.timeAttackDetector.Valid(hello.Time); err != nil {
|
||||
p.logger.InfoError("invalid faketls time", err)
|
||||
if err := hello.Valid(p.secret.Host, p.tolerateTimeSkewness); err != nil {
|
||||
p.logger.
|
||||
BindStr("hostname", hello.Host).
|
||||
BindStr("hello-time", hello.Time.String()).
|
||||
InfoError("invalid faketls client hello", err)
|
||||
p.doDomainFronting(ctx, rewind)
|
||||
|
||||
return false
|
||||
@@ -281,19 +277,19 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
proxy := &Proxy{
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
secret: opts.Secret,
|
||||
network: opts.Network,
|
||||
antiReplayCache: opts.AntiReplayCache,
|
||||
timeAttackDetector: opts.TimeAttackDetector,
|
||||
ipBlocklist: opts.IPBlocklist,
|
||||
eventStream: opts.EventStream,
|
||||
logger: opts.getLogger("proxy"),
|
||||
domainFrontingPort: opts.getDomainFrontingPort(),
|
||||
idleTimeout: opts.getIdleTimeout(),
|
||||
bufferSize: opts.getBufferSize(),
|
||||
telegram: tg,
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
secret: opts.Secret,
|
||||
network: opts.Network,
|
||||
antiReplayCache: opts.AntiReplayCache,
|
||||
ipBlocklist: opts.IPBlocklist,
|
||||
eventStream: opts.EventStream,
|
||||
logger: opts.getLogger("proxy"),
|
||||
domainFrontingPort: opts.getDomainFrontingPort(),
|
||||
tolerateTimeSkewness: opts.getTolerateTimeSkewness(),
|
||||
idleTimeout: opts.getIdleTimeout(),
|
||||
bufferSize: opts.getBufferSize(),
|
||||
telegram: tg,
|
||||
}
|
||||
|
||||
pool, err := ants.NewPoolWithFunc(opts.getConcurrency(),
|
||||
|
||||
+17
-7
@@ -23,11 +23,6 @@ type ProxyOpts struct {
|
||||
// This is a mandatory setting.
|
||||
AntiReplayCache AntiReplayCache
|
||||
|
||||
// TimeAttackDetector defines an instance of timeattack detector.
|
||||
//
|
||||
// This is a mandatory setting.
|
||||
TimeAttackDetector TimeAttackDetector
|
||||
|
||||
// IPBlocklist defines an instance of IP blocklist.
|
||||
//
|
||||
// This is a mandatory setting.
|
||||
@@ -80,6 +75,15 @@ type ProxyOpts struct {
|
||||
// This is an optional setting.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// TolerateTimeSkewness is a time boundary that defines a time
|
||||
// range where faketls timestamp is acceptable.
|
||||
//
|
||||
// This means that if if you got a timestamp X, now is Y, then
|
||||
// if |X-Y| < TolerateTimeSkewness, then you accept a packet.
|
||||
//
|
||||
// This is an optional setting.
|
||||
TolerateTimeSkewness time.Duration
|
||||
|
||||
// PreferIP defines an IP connectivity preference. Valid values are:
|
||||
// 'prefer-ipv4', 'prefer-ipv6', 'only-ipv4', 'only-ipv6'.
|
||||
//
|
||||
@@ -97,8 +101,6 @@ func (p ProxyOpts) valid() error {
|
||||
return ErrIPBlocklistIsNotDefined
|
||||
case p.EventStream == nil:
|
||||
return ErrEventStreamIsNotDefined
|
||||
case p.TimeAttackDetector == nil:
|
||||
return ErrTimeAttackDetectorIsNotDefined
|
||||
case p.Logger == nil:
|
||||
return ErrLoggerIsNotDefined
|
||||
case !p.Secret.Valid():
|
||||
@@ -140,6 +142,14 @@ func (p ProxyOpts) getIdleTimeout() time.Duration {
|
||||
return p.IdleTimeout
|
||||
}
|
||||
|
||||
func (p ProxyOpts) getTolerateTimeSkewness() time.Duration {
|
||||
if p.TolerateTimeSkewness == 0 {
|
||||
return DefaultTolerateTimeSkewness
|
||||
}
|
||||
|
||||
return p.TolerateTimeSkewness
|
||||
}
|
||||
|
||||
func (p ProxyOpts) getPreferIP() string {
|
||||
if p.PreferIP == "" {
|
||||
return DefaultPreferIP
|
||||
|
||||
+6
-16
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/9seconds/mtg/v2/logger"
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
"github.com/9seconds/mtg/v2/network"
|
||||
"github.com/9seconds/mtg/v2/timeattack"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
@@ -46,13 +45,12 @@ func (suite *ProxyTestSuite) SetupSuite() {
|
||||
suite.NoError(err)
|
||||
|
||||
suite.opts = &mtglib.ProxyOpts{
|
||||
Secret: mtglib.GenerateSecret("httpbin.org"),
|
||||
Network: ntw,
|
||||
AntiReplayCache: antireplay.NewNoop(),
|
||||
TimeAttackDetector: timeattack.NewNoop(),
|
||||
IPBlocklist: ipblocklist.NewNoop(),
|
||||
EventStream: events.NewNoopStream(),
|
||||
Logger: logger.NewNoopLogger(),
|
||||
Secret: mtglib.GenerateSecret("httpbin.org"),
|
||||
Network: ntw,
|
||||
AntiReplayCache: antireplay.NewNoop(),
|
||||
IPBlocklist: ipblocklist.NewNoop(),
|
||||
EventStream: events.NewNoopStream(),
|
||||
Logger: logger.NewNoopLogger(),
|
||||
}
|
||||
|
||||
proxy, err := mtglib.NewProxy(*suite.opts)
|
||||
@@ -118,14 +116,6 @@ func (suite *ProxyTestSuite) TestCannotInitNoEventStream() {
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ProxyTestSuite) TestCannotInitNoTimeAttackDetector() {
|
||||
opts := *suite.opts
|
||||
opts.TimeAttackDetector = nil
|
||||
|
||||
_, err := mtglib.NewProxy(opts)
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ProxyTestSuite) TestCannotInitNoLogger() {
|
||||
opts := *suite.opts
|
||||
opts.Logger = nil
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package timeattack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
)
|
||||
|
||||
type detector struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d detector) Valid(then time.Time) error {
|
||||
now := time.Now()
|
||||
|
||||
diff := now.Sub(then)
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
|
||||
if diff > d.Duration {
|
||||
return fmt.Errorf("time is invalid. now=%d, then=%d, diff=%v",
|
||||
now.Unix(),
|
||||
then.Unix(),
|
||||
diff)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDetector returns a new TimeAttackDetector which validates that
|
||||
// timestamp belongs to intervar [X-duration, X+duration], so a small
|
||||
// timeshift is acceptable.
|
||||
func NewDetector(duration time.Duration) mtglib.TimeAttackDetector {
|
||||
return detector{
|
||||
Duration: duration,
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package timeattack_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/timeattack"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type DetectorTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *DetectorTestSuite) TestOp() {
|
||||
d := timeattack.NewDetector(time.Second)
|
||||
|
||||
suite.NoError(d.Valid(time.Now()))
|
||||
suite.NoError(d.Valid(time.Now().Add(100 * time.Millisecond)))
|
||||
suite.NoError(d.Valid(time.Now().Add(-100 * time.Millisecond)))
|
||||
suite.Error(d.Valid(time.Now().Add(time.Hour)))
|
||||
suite.Error(d.Valid(time.Now().Add(-time.Hour)))
|
||||
}
|
||||
|
||||
func TestDetector(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &DetectorTestSuite{})
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// TimeAttack has implementation of mtglib.TimeAttackDetector.
|
||||
package timeattack
|
||||
|
||||
import "time"
|
||||
|
||||
// DefaultDuration is a default duration when timestamps are acceptable.
|
||||
//
|
||||
// It means that all timestamps which are X-DefaultDuration <= X <=
|
||||
// X+DefaultDuration are fine.
|
||||
const DefaultDuration = 5 * time.Second
|
||||
@@ -1,16 +0,0 @@
|
||||
package timeattack
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
)
|
||||
|
||||
type noop struct{}
|
||||
|
||||
func (n noop) Valid(_ time.Time) error { return nil }
|
||||
|
||||
// NewNoop returns TimeAttackDetector which accepts all timestamps.
|
||||
func NewNoop() mtglib.TimeAttackDetector {
|
||||
return noop{}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package timeattack_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/timeattack"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type NoopTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *NoopTestSuite) TestOp() {
|
||||
d := timeattack.NewNoop()
|
||||
|
||||
suite.NoError(d.Valid(time.Now()))
|
||||
suite.NoError(d.Valid(time.Now().Add(time.Hour)))
|
||||
suite.NoError(d.Valid(time.Now().Add(-time.Hour)))
|
||||
}
|
||||
|
||||
func TestNoop(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &NoopTestSuite{})
|
||||
}
|
||||
Reference in New Issue
Block a user