diff --git a/antireplay/doc.go b/antireplay/doc.go new file mode 100644 index 0000000..7e86edb --- /dev/null +++ b/antireplay/doc.go @@ -0,0 +1,7 @@ +// Antireplay package has cache implementations that are effective +// against replay attacks. +// +// To understand more about replay attacks, please read documentation +// for mtglib.AntiReplayCache interface. This package has a list of some +// implementations of this interface. +package antireplay diff --git a/antireplay/init.go b/antireplay/init.go index 84fd82e..1f17681 100644 --- a/antireplay/init.go +++ b/antireplay/init.go @@ -1,6 +1,11 @@ package antireplay const ( - DefaultMaxSize = 1024 * 1024 // 1MiB - DefaultErrorRate = 0.001 + // DefaultStableBloomFilterMaxSize is a recommended byte size for a + // stable bloom filter. + DefaultStableBloomFilterMaxSize = 1024 * 1024 // 1MiB + + // DefaultStableBloomFilterErrorRate is a recommended default error + // rate for a stable bloom filter. + DefaultStableBloomFilterErrorRate = 0.001 ) diff --git a/antireplay/noop.go b/antireplay/noop.go index 8613e40..e4e7cf4 100644 --- a/antireplay/noop.go +++ b/antireplay/noop.go @@ -6,6 +6,9 @@ type noop struct{} func (n noop) SeenBefore(_ []byte) bool { return false } +// NewNoop returns an implementation that does nothing. A corresponding +// method always returns false, so this cache accepts everything you +// pass to it. func NewNoop() mtglib.AntiReplayCache { return noop{} } diff --git a/antireplay/stable_bloom_filter.go b/antireplay/stable_bloom_filter.go index b52631e..4e075b7 100644 --- a/antireplay/stable_bloom_filter.go +++ b/antireplay/stable_bloom_filter.go @@ -20,6 +20,18 @@ func (s *stableBloomFilter) SeenBefore(digest []byte) bool { return s.filter.TestAndAdd(digest) } +// NewStableBloomFilter returns an implementation of AntiReplayCache +// based on stable bloom filter. +// +// http://webdocs.cs.ualberta.ca/~drafiei/papers/DupDet06Sigmod.pdf +// +// The basic idea of a stable bloom filter is quite simple: each time +// when you set a new element, you randomly reset P elements. There is a +// hardcore math which proves that if you choose this P correctly, you +// can maintain the same error rate for a stream of elements. +// +// byteSize is the number of bytes you want to give to a bloom filter . +// errorRate is desired false-positive error rate . func NewStableBloomFilter(byteSize uint, errorRate float64) mtglib.AntiReplayCache { sf := boom.NewDefaultStableBloomFilter(byteSize*8, errorRate) // nolint: gomnd sf.SetHash(xxhash.New64()) diff --git a/internal/cli/proxy.go b/internal/cli/proxy.go index 4b54d8d..fed863a 100644 --- a/internal/cli/proxy.go +++ b/internal/cli/proxy.go @@ -93,8 +93,8 @@ func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) { } opts.AntiReplayCache = antireplay.NewStableBloomFilter( - c.Config.Defense.AntiReplay.MaxSize.Value(antireplay.DefaultMaxSize), - c.Config.Defense.AntiReplay.ErrorRate.Value(antireplay.DefaultErrorRate), + c.Config.Defense.AntiReplay.MaxSize.Value(antireplay.DefaultStableBloomFilterMaxSize), + c.Config.Defense.AntiReplay.ErrorRate.Value(antireplay.DefaultStableBloomFilterErrorRate), ) } diff --git a/mtglib/init.go b/mtglib/init.go index 010a78c..321da03 100644 --- a/mtglib/init.go +++ b/mtglib/init.go @@ -33,7 +33,39 @@ type Network interface { MakeHTTPClient(func(ctx context.Context, network, address string) (net.Conn, error)) *http.Client } +// AntiReplayCache is an interface that is used to detect replay attacks +// based on some traffic fingerprints. +// +// Replay attacks are probe attacks whose main goal is to identify if +// server software can be classified in some way. For example, if you +// send some HTTP request to a web server, then you can expect that this +// server will respond with HTTP response back. +// +// There is a problem though. Let's imagine, that connection is +// encrypted. Let's imagine, that it is encrypted with some static key +// like ShadowSocks (https://shadowsocks.org/assets/whitepaper.pdf). +// In that case, in theory, if you repeat the same bytes, you can get +// the same responses. Let's imagine, that you've cracked the key. then +// if you send the same bytes, you can decrypt a response and see its +// structure. Based on its structure you can identify if this server is +// SOCKS5, MTPROTO proxy etc. +// +// This is just one example, maybe not the best or not the most +// relevant. In real life, different organizations use such replay +// attacks to perform some reverse engineering of the proxy, do some +// statical analysis to identify server software. +// +// There are many ways how to protect your proxy against them. One +// is domain fronting which is a core part of mtg. Another one is to +// collect some 'handshake fingerprints' and forbid duplication. +// +// So, it one is sending the same byte flow right after you (or a couple +// of hours after), mtg should detect that and reject this connection +// (or redirect to fronting domain). type AntiReplayCache interface { + // Seen before checks if this set of bytes was observed before or + // not. If it is required to store this information somewhere else, + // then it has to do that. SeenBefore(data []byte) bool }