mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 13:14:02 +03:00
Add separate handshake timeout
This PR adds a new setting to the config: `network.timeout`. This setting defines a time period during which all handshake procedures and ceremonies must be completed. If not - connection is aborted. This should help in situations when connection is established but client cannot continue for some reason (for example, RST sent by some middle box).
This commit is contained in:
+1
-3
@@ -205,13 +205,11 @@ proxies = [
|
||||
# means a timeout on pumping data between sockset when nothing is
|
||||
# happening.
|
||||
#
|
||||
# please be noticed that handshakes have no timeouts intentionally. You can
|
||||
# find a reasoning here:
|
||||
# https://www.ndss-symposium.org/wp-content/uploads/2020/02/23087-paper.pdf
|
||||
[network.timeout]
|
||||
tcp = "5s"
|
||||
http = "10s"
|
||||
idle = "5m"
|
||||
handshake = "10s"
|
||||
|
||||
# mtg has to mimic real websites. It does not mean domain fronting, it also
|
||||
# means that traffic characteristics should be similar to real world traffic.
|
||||
|
||||
@@ -263,6 +263,7 @@ func runProxy(conf *config.Config, version string) error { //nolint: funlen
|
||||
AllowFallbackOnUnknownDC: conf.AllowFallbackOnUnknownDC.Get(false),
|
||||
TolerateTimeSkewness: conf.TolerateTimeSkewness.Value,
|
||||
IdleTimeout: conf.Network.Timeout.Idle.Get(mtglib.DefaultIdleTimeout),
|
||||
HandshakeTimeout: conf.Network.Timeout.Handshake.Get(mtglib.DefaultHandshakeTimeout),
|
||||
|
||||
DoppelGangerURLs: doppelGangerURLs,
|
||||
DoppelGangerPerRaid: conf.Defense.Doppelganger.Repeats.Get(mtglib.DoppelGangerPerRaid),
|
||||
|
||||
@@ -60,9 +60,10 @@ type Config struct {
|
||||
} `json:"defense"`
|
||||
Network struct {
|
||||
Timeout struct {
|
||||
TCP TypeDuration `json:"tcp"`
|
||||
HTTP TypeDuration `json:"http"`
|
||||
Idle TypeDuration `json:"idle"`
|
||||
TCP TypeDuration `json:"tcp"`
|
||||
HTTP TypeDuration `json:"http"`
|
||||
Idle TypeDuration `json:"idle"`
|
||||
Handshake TypeDuration `json:"handshake"`
|
||||
} `json:"timeout"`
|
||||
DOHIP TypeIP `json:"dohIp"`
|
||||
DNS TypeDNSURI `json:"dns"`
|
||||
|
||||
@@ -55,9 +55,10 @@ type tomlConfig struct {
|
||||
} `toml:"defense" json:"defense,omitempty"`
|
||||
Network struct {
|
||||
Timeout struct {
|
||||
TCP string `toml:"tcp" json:"tcp,omitempty"`
|
||||
HTTP string `toml:"http" json:"http,omitempty"`
|
||||
Idle string `toml:"idle" json:"idle,omitempty"`
|
||||
TCP string `toml:"tcp" json:"tcp,omitempty"`
|
||||
HTTP string `toml:"http" json:"http,omitempty"`
|
||||
Idle string `toml:"idle" json:"idle,omitempty"`
|
||||
Handshake string `toml:"handshake" json:"handshake,omitempty"`
|
||||
} `toml:"timeout" json:"timeout,omitempty"`
|
||||
DOHIP string `toml:"doh-ip" json:"dohIp,omitempty"`
|
||||
DNS string `toml:"dns" json:"dns,omitempty"`
|
||||
|
||||
@@ -81,6 +81,10 @@ const (
|
||||
// avoid racing with MTProto ping_delay_disconnect (~60s interval).
|
||||
DefaultIdleTimeout = 5 * time.Minute
|
||||
|
||||
// DefaultHandshakeTimeout defines a time period during which the
|
||||
// all handshake ceremonies must be completed.
|
||||
DefaultHandshakeTimeout = 10 * time.Second
|
||||
|
||||
// DefaultTolerateTimeSkewness is a default timeout for time skewness on a
|
||||
// faketls timeout verification.
|
||||
DefaultTolerateTimeSkewness = 3 * time.Second
|
||||
|
||||
@@ -40,11 +40,6 @@ func ReadClientHello(
|
||||
hostname string,
|
||||
tolerateTimeSkewness time.Duration,
|
||||
) (*ClientHello, error) {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(ClientHelloReadTimeout)); err != nil {
|
||||
return nil, fmt.Errorf("cannot set read deadline: %w", err)
|
||||
}
|
||||
defer conn.SetReadDeadline(resetDeadline) //nolint: errcheck
|
||||
|
||||
// This is how FakeTLS is organized:
|
||||
// 1. We create sha256 HMAC with a given secret
|
||||
// 2. We dump there a whole TLS frame except of the fact that random
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@@ -71,11 +70,6 @@ func (suite *ParseClientHelloSnapshotTestSuite) makeConn(data []byte) *parseClie
|
||||
readBuf: readBuf,
|
||||
}
|
||||
|
||||
connMock.
|
||||
On("SetReadDeadline", mock.AnythingOfType("time.Time")).
|
||||
Twice().
|
||||
Return(nil)
|
||||
|
||||
return connMock
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/tls"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/tls/fake"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
@@ -53,11 +51,6 @@ func (suite *ParseClientHelloTestSuite) SetupTest() {
|
||||
suite.connMock = &parseClientHelloConnMock{
|
||||
readBuf: suite.readBuf,
|
||||
}
|
||||
|
||||
suite.connMock.
|
||||
On("SetReadDeadline", mock.AnythingOfType("time.Time")).
|
||||
Twice().
|
||||
Return(nil)
|
||||
}
|
||||
|
||||
func (suite *ParseClientHelloTestSuite) TearDownTest() {
|
||||
@@ -69,23 +62,11 @@ type ParseClientHello_TLSHeaderTestSuite struct {
|
||||
}
|
||||
|
||||
func (suite *ParseClientHello_TLSHeaderTestSuite) TestEmpty() {
|
||||
suite.connMock.ExpectedCalls = []*mock.Call{}
|
||||
suite.connMock.
|
||||
On("SetReadDeadline", mock.AnythingOfType("time.Time")).
|
||||
Once().
|
||||
Return(errors.New("fail"))
|
||||
|
||||
_, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime)
|
||||
suite.ErrorContains(err, "fail")
|
||||
suite.ErrorContains(err, "cannot read client hello")
|
||||
}
|
||||
|
||||
func (suite *ParseClientHello_TLSHeaderTestSuite) TestNothing() {
|
||||
suite.connMock.ExpectedCalls = []*mock.Call{}
|
||||
suite.connMock.
|
||||
On("SetReadDeadline", mock.AnythingOfType("time.Time")).
|
||||
Twice().
|
||||
Return(nil)
|
||||
|
||||
_, err := fake.ReadClientHello(suite.connMock, suite.secret.Key[:], suite.secret.Host, TolerateTime)
|
||||
suite.ErrorIs(err, io.EOF)
|
||||
}
|
||||
@@ -478,11 +459,6 @@ func (s *ParseClientHelloFragmentedTestSuite) makeConn(data []byte) *parseClient
|
||||
readBuf: readBuf,
|
||||
}
|
||||
|
||||
connMock.
|
||||
On("SetReadDeadline", mock.AnythingOfType("time.Time")).
|
||||
Twice().
|
||||
Return(nil)
|
||||
|
||||
return connMock
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,6 @@ package fake
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ClientHelloReadTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
resetDeadline time.Time
|
||||
|
||||
ErrBadDigest = errors.New("incorrect client random")
|
||||
)
|
||||
var ErrBadDigest = errors.New("incorrect client random")
|
||||
|
||||
@@ -28,6 +28,7 @@ type Proxy struct {
|
||||
allowFallbackOnUnknownDC bool
|
||||
tolerateTimeSkewness time.Duration
|
||||
idleTimeout time.Duration
|
||||
handshakeTimeout time.Duration
|
||||
domainFrontingPort int
|
||||
domainFrontingIP string
|
||||
domainFrontingProxyProtocol bool
|
||||
@@ -66,6 +67,11 @@ func (p *Proxy) ServeConn(conn essentials.Conn) {
|
||||
ctx := newStreamContext(p.ctx, p.logger, conn)
|
||||
defer ctx.Close()
|
||||
|
||||
if err := ctx.clientConn.SetDeadline(time.Now().Add(p.handshakeTimeout)); err != nil {
|
||||
ctx.logger.WarningError("cannot set handshake timeout", err)
|
||||
return
|
||||
}
|
||||
|
||||
stop := context.AfterFunc(ctx, func() {
|
||||
ctx.Close()
|
||||
})
|
||||
@@ -97,6 +103,11 @@ func (p *Proxy) ServeConn(conn essentials.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctx.clientConn.SetDeadline(time.Time{}); err != nil {
|
||||
ctx.logger.WarningError("cannot set deadline", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := p.doTelegramCall(ctx); err != nil {
|
||||
ctx.logger.WarningError("cannot dial to telegram", err)
|
||||
return
|
||||
@@ -346,6 +357,7 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
|
||||
domainFrontingIP: opts.DomainFrontingIP,
|
||||
tolerateTimeSkewness: opts.getTolerateTimeSkewness(),
|
||||
idleTimeout: opts.getIdleTimeout(),
|
||||
handshakeTimeout: opts.getHandshakeTimeout(),
|
||||
allowFallbackOnUnknownDC: opts.AllowFallbackOnUnknownDC,
|
||||
telegram: tg,
|
||||
doppelGanger: doppel.NewGanger(
|
||||
|
||||
@@ -70,6 +70,12 @@ type ProxyOpts struct {
|
||||
// This is an optional setting.
|
||||
IdleTimeout time.Duration
|
||||
|
||||
// HandshakeTimeout is a timeout during which all handshake ceremonies must
|
||||
// be completed, otherwise this process will be aborted
|
||||
//
|
||||
// This is an optional setting.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// TolerateTimeSkewness is a time boundary that defines a time range where
|
||||
// faketls timestamp is acceptable.
|
||||
//
|
||||
@@ -215,6 +221,14 @@ func (p ProxyOpts) getPreferIP() string {
|
||||
return p.PreferIP
|
||||
}
|
||||
|
||||
func (p ProxyOpts) getHandshakeTimeout() time.Duration {
|
||||
if p.HandshakeTimeout == 0 {
|
||||
return DefaultHandshakeTimeout
|
||||
}
|
||||
|
||||
return p.HandshakeTimeout
|
||||
}
|
||||
|
||||
func (p ProxyOpts) getIdleTimeout() time.Duration {
|
||||
if p.IdleTimeout == 0 {
|
||||
return DefaultIdleTimeout
|
||||
|
||||
Reference in New Issue
Block a user