Direct proxy works

This commit is contained in:
9seconds
2019-09-04 10:19:01 +03:00
parent 07985cf418
commit 2492a47d0a
87 changed files with 1883 additions and 1447 deletions
-99
View File
@@ -1,99 +0,0 @@
package wrappers
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"net"
"go.uber.org/zap"
"github.com/9seconds/mtg/utils"
"github.com/juju/errors"
)
// BlockCipher is a stream writer which encrypts/decrypts blocks of data
// with AES CBC. This also is buffered reader. It means, that block
// reading is transparent for it, you can assume you are working with
// good old io.Reader.
type BlockCipher struct {
buf *bytes.Buffer
logger *zap.SugaredLogger
conn StreamReadWriteCloser
encryptor cipher.BlockMode
decryptor cipher.BlockMode
}
func (b *BlockCipher) Read(p []byte) (int, error) {
if b.buf.Len() > 0 {
return b.flush(p)
}
buf := []byte{}
for len(buf) == 0 || len(buf)%aes.BlockSize != 0 {
rv, err := utils.ReadCurrentData(b.conn)
if err != nil {
return 0, errors.Annotate(err, "Cannot read from socket")
}
buf = append(buf, rv...)
}
b.decryptor.CryptBlocks(buf, buf)
b.buf.Write(buf) // nolint: gosec
return b.flush(p)
}
func (b *BlockCipher) flush(p []byte) (int, error) {
if b.buf.Len() <= len(p) {
sizeToReturn := b.buf.Len()
copy(p, b.buf.Bytes())
b.buf.Reset()
return sizeToReturn, nil
}
return b.buf.Read(p)
}
func (b *BlockCipher) Write(p []byte) (int, error) {
if len(p)%aes.BlockSize > 0 {
return 0, errors.Errorf("Incorrect block size %d", len(p))
}
encrypted := make([]byte, len(p))
b.encryptor.CryptBlocks(encrypted, p)
return b.conn.Write(encrypted)
}
// Logger returns an instance of the logger for this wrapper.
func (b *BlockCipher) Logger() *zap.SugaredLogger {
return b.logger
}
// LocalAddr returns local address of the underlying net.Conn.
func (b *BlockCipher) LocalAddr() *net.TCPAddr {
return b.conn.LocalAddr()
}
// RemoteAddr returns remote address of the underlying net.Conn.
func (b *BlockCipher) RemoteAddr() *net.TCPAddr {
return b.conn.RemoteAddr()
}
// Close closes underlying net.Conn.
func (b *BlockCipher) Close() error {
return b.conn.Close()
}
// NewBlockCipher creates new instance of BlockCipher based on given data.
func NewBlockCipher(conn StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser {
return &BlockCipher{
buf: &bytes.Buffer{},
conn: conn,
logger: conn.Logger().Named("block-cipher"),
encryptor: encryptor,
decryptor: decryptor,
}
}
-154
View File
@@ -1,154 +0,0 @@
package wrappers
import (
"context"
"net"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/stats"
"github.com/juju/errors"
)
// ConnPurpose is intended to be identifier of connection purpose. We
// sometimes want to treat client/telegram connection differently (for
// logging for example).
type ConnPurpose uint8
func (c ConnPurpose) String() string {
switch c {
case ConnPurposeClient:
return "client"
case ConnPurposeTelegram:
return "telegram"
}
return ""
}
// ConnPurpose* define different connection types.
const (
ConnPurposeClient = iota
ConnPurposeTelegram
)
const (
connTimeoutRead = 2 * time.Minute
connTimeoutWrite = 2 * time.Minute
)
// Conn is a basic wrapper for net.Conn providing the most low-level
// logic and management as possible.
type Conn struct {
conn net.Conn
ctx context.Context
cancel context.CancelFunc
connID string
logger *zap.SugaredLogger
publicIPv4 net.IP
publicIPv6 net.IP
}
func (c *Conn) Write(p []byte) (int, error) {
select {
case <-c.ctx.Done():
c.Close() // nolint: gosec
return 0, errors.Annotate(c.ctx.Err(), "Cannot write because context was closed")
default:
if err := c.conn.SetWriteDeadline(time.Now().Add(connTimeoutWrite)); err != nil {
c.Close() // nolint: gosec
return 0, errors.Annotate(err, "Cannot set write deadline to the socket")
}
n, err := c.conn.Write(p)
c.logger.Debugw("Write to stream", "bytes", n, "error", err)
stats.EgressTraffic(n)
if err != nil {
c.Close() // nolint: gosec
}
return n, err
}
}
func (c *Conn) Read(p []byte) (int, error) {
select {
case <-c.ctx.Done():
c.Close() // nolint: gosec
return 0, errors.Annotate(c.ctx.Err(), "Cannot read because context was closed")
default:
if err := c.conn.SetReadDeadline(time.Now().Add(connTimeoutRead)); err != nil {
c.Close() // nolint: gosec
return 0, errors.Annotate(err, "Cannot set read deadline to the socket")
}
n, err := c.conn.Read(p)
c.logger.Debugw("Read from stream", "bytes", n, "error", err)
stats.IngressTraffic(n)
if err != nil {
c.Close() // nolint: gosec
}
return n, err
}
}
// Close closes underlying net.Conn instance.
func (c *Conn) Close() error {
c.logger.Debugw("Close connection")
c.cancel()
return c.conn.Close()
}
// Logger returns an instance of the logger for this wrapper.
func (c *Conn) Logger() *zap.SugaredLogger {
return c.logger
}
// LocalAddr returns local address of the underlying net.Conn.
func (c *Conn) LocalAddr() *net.TCPAddr {
addr := c.conn.LocalAddr().(*net.TCPAddr)
newAddr := *addr
if c.RemoteAddr().IP.To4() != nil {
if c.publicIPv4 != nil {
newAddr.IP = c.publicIPv4
}
} else if c.publicIPv6 != nil {
newAddr.IP = c.publicIPv6
}
return &newAddr
}
// RemoteAddr returns remote address of the underlying net.Conn.
func (c *Conn) RemoteAddr() *net.TCPAddr {
return c.conn.RemoteAddr().(*net.TCPAddr)
}
// NewConn initializes Conn wrapper for net.Conn.
func NewConn(ctx context.Context, cancel context.CancelFunc, conn net.Conn,
connID string, purpose ConnPurpose, publicIPv4, publicIPv6 net.IP) StreamReadWriteCloser {
logger := zap.S().With(
"connection_id", connID,
"local_address", conn.LocalAddr(),
"remote_address", conn.RemoteAddr(),
"purpose", purpose,
).Named("conn")
wrapper := Conn{
conn: conn,
ctx: ctx,
cancel: cancel,
connID: connID,
logger: logger,
publicIPv4: publicIPv4,
publicIPv6: publicIPv6,
}
wrapper.logger = logger.With("faked_local_addr", wrapper.LocalAddr())
return &wrapper
}
+117
View File
@@ -0,0 +1,117 @@
package wrappers
import (
"io"
"net"
"time"
"go.uber.org/zap"
)
type Packet []byte
// Wrap is a base interface for all wrappers in this package.
type Wrap interface {
Conn() net.Conn
Logger() *zap.SugaredLogger
LocalAddr() *net.TCPAddr
RemoteAddr() *net.TCPAddr
}
type BaseReaderWithTimeout interface {
ReadTimeout([]byte, time.Duration) (int, error)
}
type BaseWriterWithTimeout interface {
WriteTimeout([]byte, time.Duration) (int, error)
}
type BasePacketReader interface {
Read() (Packet, error)
}
type BasePacketWriter interface {
Write(Packet) error
}
type StreamReader interface {
Wrap
io.Reader
BaseReaderWithTimeout
}
type StreamWriter interface {
Wrap
io.Writer
BaseWriterWithTimeout
}
type StreamCloser interface {
Wrap
io.Closer
}
type StreamReadCloser interface {
Wrap
io.ReadCloser
BaseReaderWithTimeout
}
type StreamWriteCloser interface {
Wrap
io.WriteCloser
BaseWriterWithTimeout
}
type StreamReadWriter interface {
Wrap
io.ReadWriter
BaseReaderWithTimeout
}
type StreamReadWriteCloser interface {
Wrap
io.ReadWriteCloser
BaseReaderWithTimeout
BaseWriterWithTimeout
}
type PacketReader interface {
Wrap
BasePacketReader
}
type PacketWriter interface {
Wrap
BasePacketWriter
}
type PacketCloser interface {
Wrap
io.Closer
}
type PacketReadCloser interface {
Wrap
BasePacketReader
io.Closer
}
type PacketWriteCloser interface {
Wrap
BasePacketWriter
io.Closer
}
type PacketReadWriter interface {
Wrap
BasePacketWriter
BasePacketReader
}
type PacketReadWriteCloser interface {
Wrap
BasePacketWriter
BasePacketReader
io.Closer
}
-159
View File
@@ -1,159 +0,0 @@
package wrappers
import (
"bytes"
"io"
"net"
"github.com/juju/errors"
"go.uber.org/zap"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/utils"
)
const (
mtprotoAbridgedSmallPacketLength = 0x7f
mtprotoAbridgedQuickAckLength = 0x80
mtprotoAbridgedLargePacketLength = 16777216 // 256 ^ 3
)
// MTProtoAbridged presents abridged connection between client and
// middle proxy.
type MTProtoAbridged struct {
conn StreamReadWriteCloser
opts *mtproto.ConnectionOpts
logger *zap.SugaredLogger
readCounter uint32
writeCounter uint32
}
func (m *MTProtoAbridged) Read() ([]byte, error) {
defer func() {
m.readCounter++
}()
m.logger.Debugw("Read packet",
"simple_ack", m.opts.ReadHacks.SimpleAck,
"quick_ack", m.opts.ReadHacks.QuickAck,
"counter", m.readCounter,
)
buf := &bytes.Buffer{}
buf.Grow(3)
if _, err := io.CopyN(buf, m.conn, 1); err != nil {
return nil, errors.Annotate(err, "Cannot read message length")
}
msgLength := uint32(buf.Bytes()[0])
buf.Reset()
m.logger.Debugw("Packet first byte",
"byte", msgLength,
"counter", m.readCounter,
"simple_ack", m.opts.ReadHacks.SimpleAck,
"quick_ack", m.opts.ReadHacks.QuickAck,
)
if msgLength >= mtprotoAbridgedQuickAckLength {
m.opts.ReadHacks.QuickAck = true
msgLength -= mtprotoAbridgedQuickAckLength
}
if msgLength == mtprotoAbridgedSmallPacketLength {
if _, err := io.CopyN(buf, m.conn, 3); err != nil {
return nil, errors.Annotate(err, "Cannot read the correct message length")
}
number := utils.Uint24{}
copy(number[:], buf.Bytes())
msgLength = utils.FromUint24(number)
}
msgLength *= 4
m.logger.Debugw("Packet length",
"length", msgLength,
"simple_ack", m.opts.ReadHacks.SimpleAck,
"quick_ack", m.opts.ReadHacks.QuickAck,
"counter", m.readCounter,
)
buf.Reset()
buf.Grow(int(msgLength))
if _, err := io.CopyN(buf, m.conn, int64(msgLength)); err != nil {
return nil, errors.Annotate(err, "Cannot read message")
}
return buf.Bytes(), nil
}
func (m *MTProtoAbridged) Write(p []byte) (int, error) {
defer func() {
m.writeCounter++
}()
m.logger.Debugw("Write packet",
"length", len(p),
"simple_ack", m.opts.WriteHacks.SimpleAck,
"quick_ack", m.opts.WriteHacks.QuickAck,
"counter", m.writeCounter,
)
if len(p)%4 != 0 {
return 0, errors.Errorf("Incorrect packet length %d", len(p))
}
if m.opts.WriteHacks.SimpleAck {
return m.conn.Write(utils.ReverseBytes(p))
}
packetLength := len(p) / 4
switch {
case packetLength < mtprotoAbridgedSmallPacketLength:
newData := append([]byte{byte(packetLength)}, p...)
return m.conn.Write(newData)
case packetLength < mtprotoAbridgedLargePacketLength:
length24 := utils.ToUint24(uint32(packetLength))
buf := &bytes.Buffer{}
buf.Grow(1 + 3 + len(p))
buf.WriteByte(byte(mtprotoAbridgedSmallPacketLength)) // nolint: gosec
buf.Write(length24[:]) // nolint: gosec
buf.Write(p) // nolint: gosec
return m.conn.Write(buf.Bytes())
}
return 0, errors.Errorf("Packet is too big %d", len(p))
}
// Logger returns an instance of the logger for this wrapper.
func (m *MTProtoAbridged) Logger() *zap.SugaredLogger {
return m.logger
}
// LocalAddr returns local address of the underlying net.Conn.
func (m *MTProtoAbridged) LocalAddr() *net.TCPAddr {
return m.conn.LocalAddr()
}
// RemoteAddr returns remote address of the underlying net.Conn.
func (m *MTProtoAbridged) RemoteAddr() *net.TCPAddr {
return m.conn.RemoteAddr()
}
// Close closes underlying net.Conn instance.
func (m *MTProtoAbridged) Close() error {
return m.conn.Close()
}
// NewMTProtoAbridged creates new wrapper for abridged client connection.
func NewMTProtoAbridged(conn StreamReadWriteCloser, opts *mtproto.ConnectionOpts) PacketReadWriteCloser {
return &MTProtoAbridged{
conn: conn,
opts: opts,
logger: conn.Logger().Named("mtproto-abridged"),
}
}
-98
View File
@@ -1,98 +0,0 @@
package wrappers
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5" // nolint: gas
"crypto/sha1" // nolint: gosec
"encoding/binary"
"net"
"github.com/9seconds/mtg/mtproto/rpc"
"github.com/9seconds/mtg/utils"
)
type cipherPurpose uint8
const (
cipherPurposeClient cipherPurpose = iota
cipherPurposeServer
)
var emptyIP = [4]byte{0x00, 0x00, 0x00, 0x00}
// NewMiddleProxyCipher creates new block cipher to proxy<->telegram
// connection.
func NewMiddleProxyCipher(conn StreamReadWriteCloser,
req *rpc.NonceRequest, resp *rpc.NonceResponse, secret []byte) StreamReadWriteCloser {
localAddr := conn.LocalAddr()
remoteAddr := conn.RemoteAddr()
encKey, encIV := deriveKeys(cipherPurposeClient, req, resp, localAddr, remoteAddr, secret)
decKey, decIV := deriveKeys(cipherPurposeServer, req, resp, localAddr, remoteAddr, secret)
enc, _ := makeEncrypterDecrypter(encKey, encIV)
_, dec := makeEncrypterDecrypter(decKey, decIV)
return NewBlockCipher(conn, enc, dec)
}
func deriveKeys(purpose cipherPurpose, req *rpc.NonceRequest, resp *rpc.NonceResponse,
client, remote *net.TCPAddr, secret []byte) ([]byte, []byte) {
message := bytes.Buffer{}
message.Write(resp.Nonce) // nolint: gosec
message.Write(req.Nonce) // nolint: gosec
message.Write(req.CryptoTS) // nolint: gosec
clientIPv4 := emptyIP[:]
serverIPv4 := emptyIP[:]
if client.IP.To4() != nil {
clientIPv4 = utils.ReverseBytes(client.IP.To4())
serverIPv4 = utils.ReverseBytes(remote.IP.To4())
}
message.Write(serverIPv4) // nolint: gosec
var port [2]byte
binary.LittleEndian.PutUint16(port[:], uint16(client.Port))
message.Write(port[:]) // nolint: gosec
switch purpose {
case cipherPurposeClient:
message.WriteString("CLIENT") // nolint: gosec
case cipherPurposeServer:
message.WriteString("SERVER") // nolint: gosec
default:
panic("Unexpected cipher purpose")
}
message.Write(clientIPv4) // nolint: gosec
binary.LittleEndian.PutUint16(port[:], uint16(remote.Port))
message.Write(port[:]) // nolint: gosec
message.Write(secret) // nolint: gosec
message.Write(resp.Nonce) // nolint: gosec
if client.IP.To4() == nil {
message.Write(client.IP.To16()) // nolint: gosec
message.Write(remote.IP.To16()) // nolint: gosec
}
message.Write(req.Nonce) // nolint: gosec
data := message.Bytes()
md5sum := md5.Sum(data[1:]) // nolint: gas
sha1sum := sha1.Sum(data) // nolint: gosec
key := append(md5sum[:12], sha1sum[:]...)
iv := md5.Sum(data[2:]) // nolint: gas
return key, iv[:]
}
func makeEncrypterDecrypter(key, iv []byte) (cipher.BlockMode, cipher.BlockMode) {
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
return cipher.NewCBCEncrypter(block, iv), cipher.NewCBCDecrypter(block, iv)
}
-161
View File
@@ -1,161 +0,0 @@
package wrappers
import (
"bytes"
"crypto/aes"
"encoding/binary"
"hash/crc32"
"io"
"io/ioutil"
"net"
"github.com/juju/errors"
"go.uber.org/zap"
)
const (
mtprotoFrameMinMessageLength = 12
mtprotoFrameMaxMessageLength = 16777216
)
var mtprotoFramePadding = []byte{0x04, 0x00, 0x00, 0x00}
// MTProtoFrame is a wrapper which converts written data to the MTProtoFrame.
// The format of the frame:
//
// [ MSGLEN(4) | SEQNO(4) | MSG(...) | CRC32(4) | PADDING(4*x) ]
//
// MSGLEN is the length of the message + len of seqno and msglen.
// SEQNO is the number of frame in the receive/send sequence. If client
// sends a message with SeqNo 18, it has to receive message with SeqNo 18.
// MSG is the data which has to be written
// CRC32 is the CRC32 checksum of MSGLEN + SEQNO + MSG
// PADDING is custom padding schema to complete frame length to such that
// len(frame) % 16 == 0
type MTProtoFrame struct {
conn StreamReadWriteCloser
logger *zap.SugaredLogger
readSeqNo int32
writeSeqNo int32
}
func (m *MTProtoFrame) Read() ([]byte, error) { // nolint: gocyclo
buf := &bytes.Buffer{}
sum := crc32.NewIEEE()
writer := io.MultiWriter(buf, sum)
for {
buf.Reset()
sum.Reset()
if _, err := io.CopyN(writer, m.conn, 4); err != nil {
return nil, errors.Annotate(err, "Cannot read frame padding")
}
if !bytes.Equal(buf.Bytes(), mtprotoFramePadding) {
break
}
}
messageLength := binary.LittleEndian.Uint32(buf.Bytes())
m.logger.Debugw("Read MTProto frame",
"messageLength", messageLength,
"sequence_number", m.readSeqNo,
)
if messageLength%4 != 0 || messageLength < mtprotoFrameMinMessageLength ||
messageLength > mtprotoFrameMaxMessageLength {
return nil, errors.Errorf("Incorrect frame message length %d", messageLength)
}
buf.Reset()
buf.Grow(int(messageLength) - 4 - 4)
if _, err := io.CopyN(writer, m.conn, int64(messageLength)-4-4); err != nil {
return nil, errors.Annotate(err, "Cannot read the message frame")
}
var seqNo int32
binary.Read(buf, binary.LittleEndian, &seqNo) // nolint: errcheck, gosec
if seqNo != m.readSeqNo {
return nil, errors.Errorf("Unexpected sequence number %d (wait for %d)", seqNo, m.readSeqNo)
}
data, _ := ioutil.ReadAll(buf) // nolint: gosec
buf.Reset()
// write to buf, not to writer. This is because we are going to fetch
// crc32 checksum.
if _, err := io.CopyN(buf, m.conn, 4); err != nil {
return nil, errors.Annotate(err, "Cannot read checksum")
}
checksum := binary.LittleEndian.Uint32(buf.Bytes())
if checksum != sum.Sum32() {
return nil, errors.Errorf("CRC32 checksum mismatch. Wait for %d, got %d", sum.Sum32(), checksum)
}
m.logger.Debugw("Read MTProto frame",
"messageLength", messageLength,
"sequence_number", m.readSeqNo,
"dataLength", len(data),
"checksum", checksum,
)
m.readSeqNo++
return data, nil
}
func (m *MTProtoFrame) Write(p []byte) (int, error) {
messageLength := 4 + 4 + len(p) + 4
paddingLength := (aes.BlockSize - messageLength%aes.BlockSize) % aes.BlockSize
buf := &bytes.Buffer{}
buf.Grow(messageLength + paddingLength)
binary.Write(buf, binary.LittleEndian, uint32(messageLength)) // nolint: errcheck, gosec
binary.Write(buf, binary.LittleEndian, m.writeSeqNo) // nolint: errcheck, gosec
buf.Write(p) // nolint: gosec
checksum := crc32.ChecksumIEEE(buf.Bytes())
binary.Write(buf, binary.LittleEndian, checksum) // nolint: errcheck, gosec
buf.Write(bytes.Repeat(mtprotoFramePadding, paddingLength/4)) // nolint: gosec
m.logger.Debugw("Write MTProto frame",
"length", len(p),
"sequence_number", m.writeSeqNo,
"crc32", checksum,
"frame_length", buf.Len(),
)
m.writeSeqNo++
_, err := m.conn.Write(buf.Bytes())
return len(p), err
}
// Logger returns an instance of the logger for this wrapper.
func (m *MTProtoFrame) Logger() *zap.SugaredLogger {
return m.logger
}
// LocalAddr returns local address of the underlying net.Conn.
func (m *MTProtoFrame) LocalAddr() *net.TCPAddr {
return m.conn.LocalAddr()
}
// RemoteAddr returns remote address of the underlying net.Conn.
func (m *MTProtoFrame) RemoteAddr() *net.TCPAddr {
return m.conn.RemoteAddr()
}
// Close closes underlying net.Conn instance.
func (m *MTProtoFrame) Close() error {
return m.conn.Close()
}
// NewMTProtoFrame creates new PacketWrapper for underlying connection.
func NewMTProtoFrame(conn StreamReadWriteCloser, seqNo int32) PacketReadWriteCloser {
return &MTProtoFrame{
conn: conn,
logger: conn.Logger().Named("mtproto-frame"),
readSeqNo: seqNo,
writeSeqNo: seqNo,
}
}
-117
View File
@@ -1,117 +0,0 @@
package wrappers
import (
"bytes"
"encoding/binary"
"io"
"net"
"github.com/juju/errors"
"go.uber.org/zap"
"github.com/9seconds/mtg/mtproto"
)
const mtprotoIntermediateQuickAckLength = 0x80000000
// MTProtoIntermediate presents intermediate connection between client
// and Telegram.
type MTProtoIntermediate struct {
conn StreamReadWriteCloser
opts *mtproto.ConnectionOpts
logger *zap.SugaredLogger
readCounter uint32
writeCounter uint32
}
func (m *MTProtoIntermediate) Read() ([]byte, error) {
defer func() {
m.readCounter++
}()
m.logger.Debugw("Read packet",
"simple_ack", m.opts.ReadHacks.SimpleAck,
"quick_ack", m.opts.ReadHacks.QuickAck,
"counter", m.readCounter,
)
buf := &bytes.Buffer{}
buf.Grow(4)
if _, err := io.CopyN(buf, m.conn, 4); err != nil {
return nil, errors.Annotate(err, "Cannot read message length")
}
length := binary.LittleEndian.Uint32(buf.Bytes())
m.logger.Debugw("Packet message length",
"simple_ack", m.opts.ReadHacks.SimpleAck,
"quick_ack", m.opts.ReadHacks.QuickAck,
"counter", m.readCounter,
"length", length,
)
if length > mtprotoIntermediateQuickAckLength {
m.opts.ReadHacks.QuickAck = true
length -= mtprotoIntermediateQuickAckLength
}
buf.Reset()
buf.Grow(int(length))
if _, err := io.CopyN(buf, m.conn, int64(length)); err != nil {
return nil, errors.Annotate(err, "Cannot read the message")
}
return buf.Bytes()[:length], nil
}
func (m *MTProtoIntermediate) Write(p []byte) (int, error) {
defer func() {
m.writeCounter++
}()
m.logger.Debugw("Write packet",
"simple_ack", m.opts.WriteHacks.SimpleAck,
"quick_ack", m.opts.WriteHacks.QuickAck,
"counter", m.writeCounter,
)
if m.opts.WriteHacks.SimpleAck {
return m.conn.Write(p)
}
var length [4]byte
binary.LittleEndian.PutUint32(length[:], uint32(len(p)))
return m.conn.Write(append(length[:], p...))
}
// Logger returns an instance of the logger for this wrapper.
func (m *MTProtoIntermediate) Logger() *zap.SugaredLogger {
return m.logger
}
// LocalAddr returns local address of the underlying net.Conn.
func (m *MTProtoIntermediate) LocalAddr() *net.TCPAddr {
return m.conn.LocalAddr()
}
// RemoteAddr returns remote address of the underlying net.Conn.
func (m *MTProtoIntermediate) RemoteAddr() *net.TCPAddr {
return m.conn.RemoteAddr()
}
// Close closes underlying net.Conn instance.
func (m *MTProtoIntermediate) Close() error {
return m.conn.Close()
}
// NewMTProtoIntermediate creates new PacketWrapper for intermediate
// client connection.
func NewMTProtoIntermediate(conn StreamReadWriteCloser, opts *mtproto.ConnectionOpts) PacketReadWriteCloser {
return &MTProtoIntermediate{
conn: conn,
logger: conn.Logger().Named("mtproto-intermediate"),
opts: opts,
}
}
-74
View File
@@ -1,74 +0,0 @@
package wrappers
import (
"bytes"
"encoding/binary"
"math/rand"
"github.com/9seconds/mtg/mtproto"
)
// MTProtoIntermediateSecure is an extension of MTProtoIntermediate
// mode which supports random paddings (socalled 'secure mode' or
// 'dd-secrets').
type MTProtoIntermediateSecure struct {
MTProtoIntermediate
}
func (m *MTProtoIntermediateSecure) Read() ([]byte, error) {
data, err := m.MTProtoIntermediate.Read()
if err != nil {
return nil, err
}
length := len(data) - (len(data) % 4)
return data[:length], nil
}
func (m *MTProtoIntermediateSecure) Write(p []byte) (int, error) {
defer func() {
m.writeCounter++
}()
m.logger.Debugw("Write packet",
"simple_ack", m.opts.WriteHacks.SimpleAck,
"quick_ack", m.opts.WriteHacks.QuickAck,
"counter", m.writeCounter,
)
if m.opts.WriteHacks.SimpleAck {
return m.conn.Write(p)
}
buf := &bytes.Buffer{}
paddingLength := rand.Intn(4)
buf.Grow(4 + len(p) + paddingLength)
binary.Write(buf, binary.LittleEndian, uint32(len(p)+paddingLength)) // nolint: errcheck, gosec
buf.Write(p) // nolint: gosec
buf.Write(make([]byte, paddingLength)) // nolint: gosec
m.logger.Debugw("Write packet with padding",
"simple_ack", m.opts.WriteHacks.SimpleAck,
"quick_ack", m.opts.WriteHacks.QuickAck,
"counter", m.writeCounter,
"padding_length", paddingLength,
"length", len(p),
)
_, err := m.conn.Write(buf.Bytes())
return len(p), err
}
// NewMTProtoIntermediateSecure create new instance of
// MTProtoIntermediateSecure instance.
func NewMTProtoIntermediateSecure(conn StreamReadWriteCloser, opts *mtproto.ConnectionOpts) PacketReadWriteCloser {
return &MTProtoIntermediateSecure{
MTProtoIntermediate: MTProtoIntermediate{
conn: conn,
logger: conn.Logger().Named("mtproto-intermediate-secure"),
opts: opts,
},
}
}
-165
View File
@@ -1,165 +0,0 @@
package wrappers
import (
"bytes"
"fmt"
"net"
"github.com/juju/errors"
"go.uber.org/zap"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/mtproto/rpc"
)
// MTProtoProxy is a wrapper which creates/reads RPC responses from Telegram.
type MTProtoProxy struct {
conn PacketReadWriteCloser
req *rpc.ProxyRequest
logger *zap.SugaredLogger
readCounter uint32
writeCounter uint32
}
func (m *MTProtoProxy) Read() ([]byte, error) {
defer func() {
m.readCounter++
}()
m.logger.Debugw("Read packet",
"counter", m.readCounter,
"simple_ack", m.req.Options.WriteHacks.SimpleAck,
"quick_ack", m.req.Options.WriteHacks.QuickAck,
)
packet, err := m.conn.Read()
if err != nil {
return nil, errors.Annotate(err, "Cannot read packet")
}
m.logger.Debugw("Read packet length",
"counter", m.readCounter,
"simple_ack", m.req.Options.WriteHacks.SimpleAck,
"quick_ack", m.req.Options.WriteHacks.QuickAck,
"length", len(packet),
)
if len(packet) < 4 {
return nil, errors.Annotate(err, "Incorrect packet length")
}
tag, packet := packet[:4], packet[4:]
switch {
case bytes.Equal(tag, rpc.TagProxyAns):
return m.readProxyAns(packet)
case bytes.Equal(tag, rpc.TagSimpleAck):
return m.readSimpleAck(packet)
case bytes.Equal(tag, rpc.TagCloseExt):
return m.readCloseExt()
}
return nil, errors.Errorf("Unknown RPC answer %v", tag)
}
func (m *MTProtoProxy) readProxyAns(data []byte) ([]byte, error) {
if len(data) < 12 {
return nil, errors.Errorf("Incorrect data of proxy answer: %d", len(data))
}
data = data[12:]
m.logger.Debugw("Read RPC_PROXY_ANS",
"counter", m.readCounter,
"length", len(data),
)
return data, nil
}
func (m *MTProtoProxy) readSimpleAck(data []byte) ([]byte, error) {
if len(data) != 12 {
return nil, errors.Errorf("Incorrect data of simple ack: %d", len(data))
}
data = data[8:12]
m.req.Options.WriteHacks.SimpleAck = true
m.logger.Debugw("Read RPC_SIMPLE_ACK",
"counter", m.readCounter,
"length", len(data),
)
return data, nil
}
func (m *MTProtoProxy) readCloseExt() ([]byte, error) {
m.logger.Debugw("Read RPC_CLOSE_EXT", "counter", m.readCounter)
return nil, errors.New("Connection has been closed remotely by RPC call")
}
func (m *MTProtoProxy) Write(p []byte) (int, error) {
defer func() {
m.writeCounter++
}()
m.logger.Debugw("Write packet",
"length", len(p),
"counter", m.writeCounter,
"simple_ack", m.req.Options.ReadHacks.SimpleAck,
"quick_ack", m.req.Options.ReadHacks.QuickAck,
)
header, flags := m.req.MakeHeader(p)
if ce := m.logger.Desugar().Check(zap.DebugLevel, "RPC_PROXY_REQ header"); ce != nil {
ce.Write(
zap.Int("length", len(p)),
zap.Uint32("counter", m.writeCounter),
zap.Bool("simple_ack", m.req.Options.ReadHacks.QuickAck),
zap.Bool("quick_ack", m.req.Options.ReadHacks.SimpleAck),
zap.String("header", fmt.Sprintf("%v", header.Bytes())),
zap.Stringer("flags", flags),
)
}
header.Write(p) // nolint: gosec
if _, err := m.conn.Write(header.Bytes()); err != nil {
return 0, err
}
return len(p), nil
}
// Logger returns an instance of the logger for this wrapper.
func (m *MTProtoProxy) Logger() *zap.SugaredLogger {
return m.logger
}
// LocalAddr returns local address of the underlying net.Conn.
func (m *MTProtoProxy) LocalAddr() *net.TCPAddr {
return m.conn.LocalAddr()
}
// RemoteAddr returns remote address of the underlying net.Conn.
func (m *MTProtoProxy) RemoteAddr() *net.TCPAddr {
return m.conn.RemoteAddr()
}
// Close closes underlying net.Conn instance.
func (m *MTProtoProxy) Close() error {
return m.conn.Close()
}
// NewMTProtoProxy creates new RPC wrapper.
func NewMTProtoProxy(conn PacketReadWriteCloser, connOpts *mtproto.ConnectionOpts,
adTag []byte) (PacketReadWriteCloser, error) {
req, err := rpc.NewProxyRequest(connOpts.ClientAddr, conn.LocalAddr(), connOpts, adTag)
if err != nil {
return nil, errors.Annotate(err, "Cannot create new RPC proxy request")
}
return &MTProtoProxy{
conn: conn,
logger: conn.Logger().Named("mtproto-proxy"),
req: req,
}, nil
}
-73
View File
@@ -1,73 +0,0 @@
package wrappers
import (
"bytes"
"crypto/cipher"
"net"
"github.com/juju/errors"
"go.uber.org/zap"
)
// StreamCipher is a wrapper which encrypts/decrypts stream with AES-CTR
// (as a part of obfuscated2 protocol).
type StreamCipher struct {
encryptor cipher.Stream
decryptor cipher.Stream
conn StreamReadWriteCloser
logger *zap.SugaredLogger
}
func (s *StreamCipher) Read(p []byte) (int, error) {
n, err := s.conn.Read(p)
if err != nil {
return 0, errors.Annotate(err, "Cannot read stream ciphered data")
}
s.decryptor.XORKeyStream(p, p[:n])
return n, nil
}
func (s *StreamCipher) Write(p []byte) (int, error) {
buf := streamCipherBufferPool.Get().(*bytes.Buffer)
defer streamCipherBufferPool.Put(buf)
buf.Reset()
buf.Grow(len(p))
buf.Write(p) // nolint: gosec
data := buf.Bytes()
s.encryptor.XORKeyStream(data, data)
return s.conn.Write(data)
}
// Logger returns an instance of the logger for this wrapper.
func (s *StreamCipher) Logger() *zap.SugaredLogger {
return s.logger
}
// LocalAddr returns local address of the underlying net.Conn.
func (s *StreamCipher) LocalAddr() *net.TCPAddr {
return s.conn.LocalAddr()
}
// RemoteAddr returns remote address of the underlying net.Conn.
func (s *StreamCipher) RemoteAddr() *net.TCPAddr {
return s.conn.RemoteAddr()
}
// Close closes underlying net.Conn instance.
func (s *StreamCipher) Close() error {
return s.conn.Close()
}
// NewStreamCipher creates new stream cipher wrapper.
func NewStreamCipher(conn StreamReadWriteCloser, encryptor, decryptor cipher.Stream) StreamReadWriteCloser {
return &StreamCipher{
conn: conn,
logger: conn.Logger().Named("stream-cipher"),
encryptor: encryptor,
decryptor: decryptor,
}
}
-14
View File
@@ -1,14 +0,0 @@
package wrappers
import (
"bytes"
"sync"
)
var (
streamCipherBufferPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}
)
-111
View File
@@ -1,111 +0,0 @@
package wrappers
import (
"io"
"net"
"go.uber.org/zap"
)
// Wrap is a base interface for all wrappers in this package.
type Wrap interface {
Logger() *zap.SugaredLogger
LocalAddr() *net.TCPAddr
RemoteAddr() *net.TCPAddr
}
// Writer is a base interface for writers of this package.
type Writer interface {
io.Writer
Wrap
}
// Closer is a base interface for wrappers of this package which can
// close connections.
type Closer interface {
io.Closer
Wrap
}
// WriteCloser is a base interface for wrappers of this package which
// can write to and close connections.
type WriteCloser interface {
io.Closer
Writer
}
// StreamReader is a base interface for wrappers which can read from the
// stream.
type StreamReader interface {
io.Reader
Wrap
}
// StreamReadCloser is a base interface for wrappers which can read from
// and close the connections.
type StreamReadCloser interface {
io.Closer
StreamReader
}
// StreamReadWriter is a base interface for wrappers which can read from
// and write to the connections.
type StreamReadWriter interface {
io.Writer
StreamReader
}
// StreamWriteCloser is a base interface for wrappers which can write to
// and close the connections.
type StreamWriteCloser interface {
io.WriteCloser
Wrap
}
// StreamReadWriteCloser is a base interface for stream processors.
type StreamReadWriteCloser interface {
io.Closer
StreamReadWriter
}
// PacketReader is a base interface for wrappers which reads 'packets'.
// packets are atoms so you either get a packet or you get an error You
// cannot resume reading from packet.
type PacketReader interface {
Read() ([]byte, error)
Wrap
}
// PacketWriter is a base interface for wrappers which can write packets.
type PacketWriter interface {
io.Writer
Wrap
}
// PacketReadWriter is a base interface for wrappers which can read from
// and write packets.
type PacketReadWriter interface {
io.Writer
PacketReader
}
// PacketReadCloser is a base interface for wrappers which can read
// packets and close the connection.
type PacketReadCloser interface {
io.Closer
PacketReader
}
// PacketWriteCloser is a base interface for wrappers which can write
// packets and close the connection.
type PacketWriteCloser interface {
io.Writer
io.Closer
Wrap
}
// PacketReadWriteCloser is a base interface for packet processors.
type PacketReadWriteCloser interface {
io.Closer
PacketReadWriter
}
+177
View File
@@ -0,0 +1,177 @@
package wrappers
import (
"context"
"crypto/rand"
"encoding/hex"
"net"
"time"
"github.com/juju/errors"
"go.uber.org/zap"
"github.com/9seconds/mtg/config"
)
const ConnIDLength = 8
type ConnID [ConnIDLength]byte
func (c ConnID) String() string {
return hex.EncodeToString(c[:])
}
type connPurpose uint8
const (
connPurposeClient connPurpose = 1 << iota
connPurposeTelegram
)
const (
connTimeoutRead = 2 * time.Minute
connTimeoutWrite = 2 * time.Minute
)
type wrapperConn struct {
parent net.Conn
ctx context.Context
cancel context.CancelFunc
connID ConnID
logger *zap.SugaredLogger
localAddr *net.TCPAddr
remoteAddr *net.TCPAddr
}
func (w *wrapperConn) WriteTimeout(p []byte, timeout time.Duration) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, errors.Annotate(w.ctx.Err(), "Cannot write because context was closed")
default:
if err := w.parent.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
w.Close() // nolint: gosec
return 0, errors.Annotate(err, "Cannot set write deadline to the socket")
}
n, err := w.parent.Write(p)
w.logger.Debugw("Write to stream", "bytes", n, "error", err)
if err != nil {
w.Close() // nolint: gosec
}
return n, err
}
}
func (w *wrapperConn) Write(p []byte) (int, error) {
return w.WriteTimeout(p, connTimeoutWrite)
}
func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, errors.Annotate(w.ctx.Err(), "Cannot read because context was closed")
default:
if err := w.parent.SetReadDeadline(time.Now().Add(timeout)); err != nil {
w.Close()
return 0, errors.Annotate(err, "Cannot set read deadline to the socket")
}
n, err := w.parent.Read(p)
w.logger.Debugw("Read from stream", "bytes", n, "error", err)
if err != nil {
w.Close()
}
return n, err
}
}
func (w *wrapperConn) Read(p []byte) (int, error) {
return w.ReadTimeout(p, connTimeoutRead)
}
func (w *wrapperConn) Close() error {
w.logger.Debugw("Close connection")
w.cancel()
return w.parent.Close()
}
func (w *wrapperConn) Conn() net.Conn {
return w.parent
}
func (w *wrapperConn) Logger() *zap.SugaredLogger {
return w.logger
}
func (w *wrapperConn) LocalAddr() *net.TCPAddr {
return w.localAddr
}
func (w *wrapperConn) RemoteAddr() *net.TCPAddr {
return w.remoteAddr
}
func newConn(ctx context.Context,
cancel context.CancelFunc,
parent net.Conn,
connID ConnID,
purpose connPurpose) StreamReadWriteCloser {
localAddr := *parent.LocalAddr().(*net.TCPAddr)
if parent.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {
if config.C.PublicIPv4Addr.IP != nil {
localAddr.IP = config.C.PublicIPv4Addr.IP
}
} else if config.C.PublicIPv6Addr.IP != nil {
localAddr.IP = config.C.PublicIPv6Addr.IP
}
logger := zap.S().With(
"local_address", localAddr,
"remote_address", parent.RemoteAddr(),
).Named("conn")
if purpose == connPurposeClient {
logger = logger.With("connection_id", connID.String())
}
return &wrapperConn{
parent: parent,
ctx: ctx,
cancel: cancel,
connID: connID,
logger: logger,
remoteAddr: parent.RemoteAddr().(*net.TCPAddr),
localAddr: &localAddr,
}
}
func NewClientConn(ctx context.Context,
cancel context.CancelFunc,
parent net.Conn,
connID ConnID) StreamReadWriteCloser {
return newConn(ctx, cancel, parent, connID, connPurposeClient)
}
func NewTelegramConn(ctx context.Context,
cancel context.CancelFunc,
parent net.Conn) StreamReadWriteCloser {
return newConn(ctx, cancel, parent, ConnID{}, connPurposeTelegram)
}
func NewConnID() ConnID {
var id ConnID
if _, err := rand.Read(id[:]); err != nil {
panic(err)
}
return id
}
+80
View File
@@ -0,0 +1,80 @@
package wrappers
import (
"crypto/cipher"
"net"
"time"
"github.com/juju/errors"
"go.uber.org/zap"
)
type wrapperObfuscated2 struct {
encryptor cipher.Stream
decryptor cipher.Stream
parent StreamReadWriteCloser
}
func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, error) {
n, err := w.parent.ReadTimeout(p, timeout)
if err != nil {
return 0, errors.Annotate(err, "Cannot read stream ciphered data")
}
w.decryptor.XORKeyStream(p, p[:n])
return n, nil
}
func (w *wrapperObfuscated2) Read(p []byte) (int, error) {
n, err := w.parent.Read(p)
if err != nil {
return 0, errors.Annotate(err, "Cannot read stream ciphered data")
}
w.decryptor.XORKeyStream(p, p[:n])
return n, nil
}
func (w *wrapperObfuscated2) WriteTimeout(p []byte, timeout time.Duration) (int, error) {
buf := make([]byte, len(p))
copy(buf, p)
w.encryptor.XORKeyStream(buf, buf)
return w.parent.WriteTimeout(buf, timeout)
}
func (w *wrapperObfuscated2) Write(p []byte) (int, error) {
buf := make([]byte, len(p))
copy(buf, p)
w.encryptor.XORKeyStream(buf, buf)
return w.parent.Write(buf)
}
func (w *wrapperObfuscated2) Conn() net.Conn {
return w.parent.Conn()
}
func (w *wrapperObfuscated2) Logger() *zap.SugaredLogger {
return w.parent.Logger().Named("obfuscated2")
}
func (w *wrapperObfuscated2) LocalAddr() *net.TCPAddr {
return w.parent.LocalAddr()
}
func (w *wrapperObfuscated2) RemoteAddr() *net.TCPAddr {
return w.parent.RemoteAddr()
}
func (w *wrapperObfuscated2) Close() error {
return w.parent.Close()
}
func NewObfuscated2(socket StreamReadWriteCloser, encryptor, decryptor cipher.Stream) StreamReadWriteCloser {
return &wrapperObfuscated2{
parent: socket,
encryptor: encryptor,
decryptor: decryptor,
}
}
+66
View File
@@ -0,0 +1,66 @@
package wrappers
import (
"net"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/stats"
)
type wrapperStats struct {
parent StreamReadWriteCloser
}
func (w *wrapperStats) Write(p []byte) (int, error) {
n, err := w.parent.Write(p)
stats.S.EgressTraffic(n)
return n, err
}
func (w *wrapperStats) WriteTimeout(p []byte, timeout time.Duration) (int, error) {
n, err := w.parent.WriteTimeout(p, timeout)
stats.S.EgressTraffic(n)
return n, err
}
func (w *wrapperStats) Read(p []byte) (int, error) {
n, err := w.parent.Read(p)
stats.S.IngressTraffic(n)
return n, err
}
func (w *wrapperStats) ReadTimeout(p []byte, timeout time.Duration) (int, error) {
n, err := w.parent.ReadTimeout(p, timeout)
stats.S.IngressTraffic(n)
return n, err
}
func (w *wrapperStats) Conn() net.Conn {
return w.parent.Conn()
}
func (w *wrapperStats) Logger() *zap.SugaredLogger {
return w.parent.Logger().Named("traffic")
}
func (w *wrapperStats) LocalAddr() *net.TCPAddr {
return w.parent.LocalAddr()
}
func (w *wrapperStats) RemoteAddr() *net.TCPAddr {
return w.parent.RemoteAddr()
}
func (w *wrapperStats) Close() error {
return w.parent.Close()
}
func NewTraffic(parent StreamReadWriteCloser) StreamReadWriteCloser {
return &wrapperStats{parent}
}