Rework events

This commit is contained in:
9seconds
2021-04-07 14:40:54 +03:00
parent b748603096
commit 463af648ce
13 changed files with 275 additions and 410 deletions
+5 -16
View File
@@ -6,27 +6,21 @@ import (
"io"
"net"
"sync"
"time"
)
type connTraffic struct {
net.Conn
connID string
stream EventStream
ctx context.Context
streamID string
stream EventStream
ctx context.Context
}
func (c connTraffic) Read(b []byte) (int, error) {
n, err := c.Conn.Read(b)
if n > 0 {
c.stream.Send(c.ctx, EventTraffic{
CreatedAt: time.Now(),
ConnID: c.connID,
Traffic: uint(n),
IsRead: true,
})
c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), true))
}
return n, err // nolint: wrapcheck
@@ -36,12 +30,7 @@ func (c connTraffic) Write(b []byte) (int, error) {
n, err := c.Conn.Write(b)
if n > 0 {
c.stream.Send(c.ctx, EventTraffic{
CreatedAt: time.Now(),
ConnID: c.connID,
Traffic: uint(n),
IsRead: false,
})
c.stream.Send(c.ctx, NewEventTraffic(c.streamID, uint(n), false))
}
return n, err // nolint: wrapcheck
+4 -4
View File
@@ -37,10 +37,10 @@ func (suite *ConnTrafficTestSuite) SetupTest() {
suite.eventStreamMock = &EventStreamMock{}
suite.connMock = &testlib.NetConnMock{}
suite.conn = connTraffic{
Conn: suite.connMock,
connID: "CONNID",
ctx: context.Background(),
stream: suite.eventStreamMock,
Conn: suite.connMock,
streamID: "CONNID",
ctx: context.Background(),
stream: suite.eventStreamMock,
}
}
+99 -76
View File
@@ -5,110 +5,133 @@ import (
"time"
)
type eventBase struct {
streamID string
timestamp time.Time
}
func (e eventBase) StreamID() string {
return e.streamID
}
func (e eventBase) Timestamp() time.Time {
return e.timestamp
}
type EventStart struct {
CreatedAt time.Time
ConnID string
RemoteIP net.IP
}
eventBase
func (e EventStart) StreamID() string {
return e.ConnID
}
func (e EventStart) Timestamp() time.Time {
return e.CreatedAt
RemoteIP net.IP
}
type EventConnectedToDC struct {
CreatedAt time.Time
ConnID string
RemoteIP net.IP
DC int
}
eventBase
func (e EventConnectedToDC) StreamID() string {
return e.ConnID
}
func (e EventConnectedToDC) Timestamp() time.Time {
return e.CreatedAt
RemoteIP net.IP
DC int
}
type EventTraffic struct {
CreatedAt time.Time
ConnID string
Traffic uint
IsRead bool
}
eventBase
func (e EventTraffic) StreamID() string {
return e.ConnID
}
func (e EventTraffic) Timestamp() time.Time {
return e.CreatedAt
Traffic uint
IsRead bool
}
type EventFinish struct {
CreatedAt time.Time
ConnID string
}
func (e EventFinish) StreamID() string {
return e.ConnID
}
func (e EventFinish) Timestamp() time.Time {
return e.CreatedAt
eventBase
}
type EventDomainFronting struct {
CreatedAt time.Time
ConnID string
}
func (e EventDomainFronting) StreamID() string {
return e.ConnID
}
func (e EventDomainFronting) Timestamp() time.Time {
return e.CreatedAt
eventBase
}
type EventConcurrencyLimited struct {
CreatedAt time.Time
}
func (e EventConcurrencyLimited) StreamID() string {
return ""
}
func (e EventConcurrencyLimited) Timestamp() time.Time {
return e.CreatedAt
eventBase
}
type EventIPBlocklisted struct {
CreatedAt time.Time
RemoteIP net.IP
}
eventBase
func (e EventIPBlocklisted) StreamID() string {
return ""
}
func (e EventIPBlocklisted) Timestamp() time.Time {
return e.CreatedAt
RemoteIP net.IP
}
type EventReplayAttack struct {
CreatedAt time.Time
ConnID string
eventBase
}
func (e EventReplayAttack) StreamID() string {
return e.ConnID
func NewEventStart(streamID string, remoteIP net.IP) EventStart {
return EventStart{
eventBase: eventBase{
timestamp: time.Now(),
streamID: streamID,
},
RemoteIP: remoteIP,
}
}
func (e EventReplayAttack) Timestamp() time.Time {
return e.CreatedAt
func NewEventConnectedToDC(streamID string, remoteIP net.IP, dc int) EventConnectedToDC {
return EventConnectedToDC{
eventBase: eventBase{
timestamp: time.Now(),
streamID: streamID,
},
RemoteIP: remoteIP,
DC: dc,
}
}
func NewEventTraffic(streamID string, traffic uint, isRead bool) EventTraffic {
return EventTraffic{
eventBase: eventBase{
timestamp: time.Now(),
streamID: streamID,
},
Traffic: traffic,
IsRead: isRead,
}
}
func NewEventFinish(streamID string) EventFinish {
return EventFinish{
eventBase: eventBase{
timestamp: time.Now(),
streamID: streamID,
},
}
}
func NewEventDomainFronting(streamID string) EventDomainFronting {
return EventDomainFronting{
eventBase: eventBase{
timestamp: time.Now(),
streamID: streamID,
},
}
}
func NewEventConcurrencyLimited() EventConcurrencyLimited {
return EventConcurrencyLimited{
eventBase: eventBase{
timestamp: time.Now(),
},
}
}
func NewEventIPBlocklisted(remoteIP net.IP) EventIPBlocklisted {
return EventIPBlocklisted{
eventBase: eventBase{
timestamp: time.Now(),
},
RemoteIP: remoteIP,
}
}
func NewEventReplayAttack(streamID string) EventReplayAttack {
return EventReplayAttack{
eventBase: eventBase{
timestamp: time.Now(),
streamID: streamID,
},
}
}
+8 -36
View File
@@ -14,84 +14,56 @@ type EventsTestSuite struct {
}
func (suite *EventsTestSuite) TestEventStart() {
evt := mtglib.EventStart{
CreatedAt: time.Now(),
ConnID: "CONNID",
RemoteIP: net.ParseIP("10.0.0.10"),
}
evt := mtglib.NewEventStart("CONNID", net.ParseIP("10.0.0.10"))
suite.Equal("CONNID", evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
}
func (suite *EventsTestSuite) TestEventFinish() {
evt := mtglib.EventFinish{
CreatedAt: time.Now(),
ConnID: "CONNID",
}
evt := mtglib.NewEventFinish("CONNID")
suite.Equal("CONNID", evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
}
func (suite *EventsTestSuite) TestEventConnectedToDC() {
evt := mtglib.EventConnectedToDC{
CreatedAt: time.Now(),
ConnID: "CONNID",
RemoteIP: net.ParseIP("10.0.0.10"),
DC: 3,
}
evt := mtglib.NewEventConnectedToDC("CONNID", net.ParseIP("10.0.0.10"), 3)
suite.Equal("CONNID", evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
}
func (suite *EventsTestSuite) TestEventTraffic() {
evt := mtglib.EventTraffic{
CreatedAt: time.Now(),
ConnID: "CONNID",
Traffic: 3,
IsRead: true,
}
evt := mtglib.NewEventTraffic("CONNID", 1000, true)
suite.Equal("CONNID", evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
}
func (suite *EventsTestSuite) TestEventDomainFronting() {
evt := mtglib.EventDomainFronting{
CreatedAt: time.Now(),
ConnID: "CONNID",
}
evt := mtglib.NewEventDomainFronting("CONNID")
suite.Equal("CONNID", evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
}
func (suite *EventsTestSuite) TestEventConcurrencyLimited() {
evt := mtglib.EventConcurrencyLimited{
CreatedAt: time.Now(),
}
evt := mtglib.NewEventConcurrencyLimited()
suite.Empty(evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
}
func (suite *EventsTestSuite) TestEventIPBlocklisted() {
evt := mtglib.EventIPBlocklisted{
CreatedAt: time.Now(),
RemoteIP: net.ParseIP("10.0.0.10"),
}
evt := mtglib.NewEventIPBlocklisted(net.ParseIP("10.0.0.10"))
suite.Empty(evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
}
func (suite *EventsTestSuite) TestEventReplayAttack() {
evt := mtglib.EventReplayAttack{
CreatedAt: time.Now(),
ConnID: "CONNID",
}
evt := mtglib.NewEventReplayAttack("CONNID")
suite.Equal("CONNID", evt.StreamID())
suite.WithinDuration(time.Now(), evt.Timestamp(), 10*time.Millisecond)
+71 -11
View File
@@ -9,22 +9,66 @@ import (
)
var (
ErrSecretEmpty = errors.New("secret is empty")
ErrSecretInvalid = errors.New("secret is invalid")
ErrNetworkIsNotDefined = errors.New("network is not defined")
ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined")
// ErrSecretEmpty is returned if you are trying to create a proxy
// but do not provide a secret.
ErrSecretEmpty = errors.New("secret is empty")
// ErrSecretInvalid is returned if you are trying to create a proxy
// but secret value is invalid (no host or payload are zeroes).
ErrSecretInvalid = errors.New("secret is invalid")
// ErrNetworkIsNotDefined is returned if you are trying to create a
// proxy but network value is undefined.
ErrNetworkIsNotDefined = errors.New("network is not defined")
// ErrAntiReplayCacheIsNotDefined is returned if you are trying to
// 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 = errors.New("ip blocklist is not defined")
ErrEventStreamIsNotDefined = errors.New("event stream is not defined")
ErrLoggerIsNotDefined = errors.New("logger 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")
// ErrEventStreamIsNotDefined is returned if you are trying to create a
// proxy but event stream instance is not defined.
ErrEventStreamIsNotDefined = errors.New("event stream is not defined")
// ErrLoggerIsNotDefined is returned if you are trying to
// create a proxy but logger is not defined.
ErrLoggerIsNotDefined = errors.New("logger is not defined")
)
const (
DefaultConcurrency = 4096
DefaultBufferSize = 16 * 1024 // 16 kib
// DefaultConcurrency is a default max count of simultaneously
// connected clients.
DefaultConcurrency = 4096
// DefaultBufferSize is a default size of a copy buffer.
DefaultBufferSize = 16 * 1024 // 16 kib
// DefaultDomainFrontingPort is a default port (HTTPS) to connect to in
// case of probe-resistance activity.
DefaultDomainFrontingPort = 443
DefaultIdleTimeout = time.Minute
DefaultPreferIP = "prefer-ipv6"
// DefaultIdleTimeout is a default timeout for closing a connection
// in case of idling.
DefaultIdleTimeout = time.Minute
// DefaultPreferIP is a default value for Telegram IP connectivity
// preference.
DefaultPreferIP = "prefer-ipv6"
// SecretKeyLength defines a length of the secret bytes used
// by Telegram and a proxy.
SecretKeyLength = 16
// ConnectionIDBytesLength defines a count of random bytes
// used to generate a stream/connection ids.
ConnectionIDBytesLength = 16
)
// Network defines a knowledge how to work with a network. It may sound
@@ -105,8 +149,24 @@ type IPBlocklist interface {
Contains(net.IP) bool
}
// Event is a data structure which is populated during mtg request
// processing lifecycle. Each request popluates many events:
//
// 1. Client connected
//
// 2. Request is finished
//
// 3. Connection to Telegram server is established
//
// and so on. All these events are data structures but all of them
// must conform the same interface.
type Event interface {
// StreamID returns an identifier of the stream, connection,
// request, you name it. All events within the same stream returns
// the same stream id.
StreamID() string
// Timestamp returns a timestamp when this event was generated.
Timestamp() time.Time
}
+16 -39
View File
@@ -53,18 +53,11 @@ func (p *Proxy) ServeConn(conn net.Conn) {
ctx.Close()
}()
p.eventStream.Send(ctx, EventStart{
CreatedAt: time.Now(),
ConnID: ctx.connID,
RemoteIP: ctx.ClientIP(),
})
p.eventStream.Send(ctx, NewEventStart(ctx.streamID, ctx.ClientIP()))
ctx.logger.Info("Stream has been started")
defer func() {
p.eventStream.Send(ctx, EventFinish{
CreatedAt: time.Now(),
ConnID: ctx.connID,
})
p.eventStream.Send(ctx, NewEventFinish(ctx.streamID))
ctx.logger.Info("Stream has been finished")
}()
@@ -109,10 +102,7 @@ func (p *Proxy) Serve(listener net.Listener) error {
if p.ipBlocklist.Contains(ipAddr) {
conn.Close()
logger.Info("ip was blacklisted")
p.eventStream.Send(p.ctx, EventIPBlocklisted{
CreatedAt: time.Now(),
RemoteIP: ipAddr,
})
p.eventStream.Send(p.ctx, NewEventIPBlocklisted(ipAddr))
continue
}
@@ -125,9 +115,7 @@ func (p *Proxy) Serve(listener net.Listener) error {
return nil
case errors.Is(err, ants.ErrPoolOverload):
logger.Info("connection was concurrency limited")
p.eventStream.Send(p.ctx, EventConcurrencyLimited{
CreatedAt: time.Now(),
})
p.eventStream.Send(p.ctx, NewEventConcurrencyLimited())
}
select {
@@ -181,10 +169,7 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool {
if p.antiReplayCache.SeenBefore(hello.SessionID) {
p.logger.Warning("replay attack has been detected!")
p.eventStream.Send(p.ctx, EventReplayAttack{
CreatedAt: time.Now(),
ConnID: ctx.connID,
})
p.eventStream.Send(p.ctx, NewEventReplayAttack(ctx.streamID))
p.doDomainFronting(ctx, rewind)
return false
@@ -235,31 +220,23 @@ func (p *Proxy) doTelegramCall(ctx *streamContext) error {
ctx.telegramConn = obfuscated2.Conn{
Conn: connTraffic{
Conn: conn,
connID: ctx.connID,
stream: p.eventStream,
ctx: ctx,
Conn: conn,
streamID: ctx.streamID,
stream: p.eventStream,
ctx: ctx,
},
Encryptor: encryptor,
Decryptor: decryptor,
}
p.eventStream.Send(ctx, EventConnectedToDC{
CreatedAt: time.Now(),
ConnID: ctx.connID,
RemoteIP: conn.RemoteAddr().(*net.TCPAddr).IP,
DC: ctx.dc,
})
p.eventStream.Send(ctx,
NewEventConnectedToDC(ctx.streamID, conn.RemoteAddr().(*net.TCPAddr).IP, ctx.dc))
return nil
}
func (p *Proxy) doDomainFronting(ctx *streamContext, conn *connRewind) {
p.eventStream.Send(p.ctx, EventDomainFronting{
CreatedAt: time.Now(),
ConnID: ctx.connID,
})
p.eventStream.Send(p.ctx, NewEventDomainFronting(ctx.streamID))
conn.Rewind()
frontConn, err := p.network.DialContext(ctx, "tcp", p.DomainFrontingAddress())
@@ -270,10 +247,10 @@ func (p *Proxy) doDomainFronting(ctx *streamContext, conn *connRewind) {
}
frontConn = connTraffic{
Conn: frontConn,
ctx: ctx,
connID: ctx.connID,
stream: p.eventStream,
Conn: frontConn,
ctx: ctx,
streamID: ctx.streamID,
stream: p.eventStream,
}
rel := relay.AcquireRelay(ctx,
+1 -5
View File
@@ -7,11 +7,7 @@ import (
"fmt"
)
const (
SecretKeyLength = 16
secretFakeTLSFirstByte byte = 0xee
)
const secretFakeTLSFirstByte byte = 0xee
var secretEmptyKey [SecretKeyLength]byte
+3 -5
View File
@@ -8,14 +8,12 @@ import (
"time"
)
const ConnectionIDBytesLength = 16
type streamContext struct {
ctx context.Context
ctxCancel context.CancelFunc
clientConn net.Conn
telegramConn net.Conn
connID string
streamID string
dc int
logger Logger
}
@@ -64,10 +62,10 @@ func newStreamContext(ctx context.Context, logger Logger, clientConn net.Conn) *
ctx: ctx,
ctxCancel: cancel,
clientConn: clientConn,
connID: base64.RawURLEncoding.EncodeToString(connIDBytes),
streamID: base64.RawURLEncoding.EncodeToString(connIDBytes),
}
streamCtx.logger = logger.
BindStr("stream-id", streamCtx.connID).
BindStr("stream-id", streamCtx.streamID).
BindStr("client-ip", streamCtx.ClientIP().String())
return streamCtx