Browse Source

Fix all lint errors

tags/0.9
9seconds 8 years ago
parent
commit
4ad913add2

+ 2
- 2
main.go View File

@@ -95,7 +95,7 @@ func main() {
95 95
 			usage("Cannot get local IP address.")
96 96
 		}
97 97
 		myIPBytes, err := ioutil.ReadAll(resp.Body)
98
-		resp.Body.Close()
98
+		resp.Body.Close() // nolint: errcheck
99 99
 
100 100
 		if err != nil {
101 101
 			usage("Cannot get local IP address.")
@@ -141,6 +141,6 @@ func printURLs(data interface{}) {
141 141
 }
142 142
 
143 143
 func usage(msg string) {
144
-	io.WriteString(os.Stderr, msg+"\n")
144
+	io.WriteString(os.Stderr, msg+"\n") // nolint: errcheck
145 145
 	os.Exit(1)
146 146
 }

+ 16
- 2
obfuscated2/frame.go View File

@@ -9,7 +9,6 @@ import (
9 9
 	"github.com/juju/errors"
10 10
 )
11 11
 
12
-// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
13 12
 // [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
14 13
 const (
15 14
 	frameLenKey   = 32
@@ -30,23 +29,34 @@ const (
30 29
 
31 30
 var tgMagicBytes = []byte{tgMagicByte, tgMagicByte, tgMagicByte, tgMagicByte}
32 31
 
32
+// Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2
33
+// initialization data first.
34
+// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
33 35
 type Frame []byte
34 36
 
37
+// Key returns AES encryption key.
35 38
 func (f Frame) Key() []byte {
36 39
 	return f[frameOffsetFirst:frameOffsetKey]
37 40
 }
38 41
 
42
+// IV returns AES encryption initialization vector
39 43
 func (f Frame) IV() []byte {
40 44
 	return f[frameOffsetKey:frameOffsetIV]
41 45
 }
42 46
 
47
+// Magic returns magic bytes from last 8 bytes of frame. Telegram checks
48
+// for values there. If after decryption magic is not as expected,
49
+// connection considered as failed.
43 50
 func (f Frame) Magic() []byte {
44 51
 	return f[frameOffsetIV:frameOffsetMagic]
45 52
 }
46 53
 
54
+// DC returns number of datacenter IP client wants to use.
47 55
 func (f Frame) DC() (n int16) {
48 56
 	buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC])
49
-	binary.Read(buf, binary.LittleEndian, &n)
57
+	if err := binary.Read(buf, binary.LittleEndian, &n); err != nil {
58
+		n = 1
59
+	}
50 60
 
51 61
 	if n < 0 {
52 62
 		n = -n
@@ -57,10 +67,13 @@ func (f Frame) DC() (n int16) {
57 67
 	return n - 1
58 68
 }
59 69
 
70
+// Valid checks that *decrypted* frame is valid. Only magic bytes are checked.
60 71
 func (f Frame) Valid() bool {
61 72
 	return bytes.Equal(f.Magic(), tgMagicBytes)
62 73
 }
63 74
 
75
+// Invert inverts frame for extracting encryption keys. Pkease check that link:
76
+// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
64 77
 func (f Frame) Invert() Frame {
65 78
 	reversed := make(Frame, FrameLen)
66 79
 	copy(reversed, f)
@@ -72,6 +85,7 @@ func (f Frame) Invert() Frame {
72 85
 	return reversed
73 86
 }
74 87
 
88
+// ExtractFrame extracts exact obfuscated2 handshake frame from given reader.
75 89
 func ExtractFrame(conn io.Reader) (Frame, error) {
76 90
 	buf := &bytes.Buffer{}
77 91
 	if _, err := io.CopyN(buf, conn, FrameLen); err != nil {

+ 15
- 4
obfuscated2/obfuscated2.go View File

@@ -8,35 +8,43 @@ import (
8 8
 	"github.com/juju/errors"
9 9
 )
10 10
 
11
+// Obfuscated2 contains AES CTR encryption and decryption streams
12
+// for telegram connection.
11 13
 type Obfuscated2 struct {
12 14
 	decryptor cipher.Stream
13 15
 	encryptor cipher.Stream
14 16
 }
15 17
 
18
+// Encrypt encrypts given data.
16 19
 func (o *Obfuscated2) Encrypt(data []byte) []byte {
17 20
 	buf := make([]byte, len(data))
18 21
 	o.encryptor.XORKeyStream(buf, data)
19 22
 	return buf
20 23
 }
21 24
 
25
+// Decrypt decrypts given data.
22 26
 func (o *Obfuscated2) Decrypt(data []byte) []byte {
23 27
 	buf := make([]byte, len(data))
24 28
 	o.decryptor.XORKeyStream(buf, data)
25 29
 	return buf
26 30
 }
27 31
 
32
+// ParseObfuscated2ClientFrame parses client frame. Please check this link for
33
+// details: http://telegra.ph/telegram-blocks-wtf-05-26
34
+//
35
+// Beware, link above is in russian.
28 36
 func ParseObfuscated2ClientFrame(secret, data []byte) (*Obfuscated2, int16, error) {
29 37
 	frame := Frame(data)
30 38
 
31 39
 	decHasher := sha256.New()
32
-	decHasher.Write(frame.Key())
33
-	decHasher.Write(secret)
40
+	decHasher.Write(frame.Key()) // nolint: errcheck
41
+	decHasher.Write(secret)      // nolint: errcheck
34 42
 	decryptor := makeStreamCipher(decHasher.Sum(nil), frame.IV())
35 43
 
36 44
 	invertedFrame := frame.Invert()
37 45
 	encHasher := sha256.New()
38
-	encHasher.Write(invertedFrame.Key())
39
-	encHasher.Write(secret)
46
+	encHasher.Write(invertedFrame.Key()) // nolint: errcheck
47
+	encHasher.Write(secret)              // nolint: errcheck
40 48
 	encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.IV())
41 49
 
42 50
 	decryptedFrame := make(Frame, FrameLen)
@@ -53,6 +61,9 @@ func ParseObfuscated2ClientFrame(secret, data []byte) (*Obfuscated2, int16, erro
53 61
 	return obfs, decryptedFrame.DC(), nil
54 62
 }
55 63
 
64
+// MakeTelegramObfuscated2Frame creates new handshake frame to send to
65
+// Telegram.
66
+// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
56 67
 func MakeTelegramObfuscated2Frame() (*Obfuscated2, Frame) {
57 68
 	frame := generateFrame()
58 69
 

+ 0
- 1
obfuscated2/obfuscated2_test.go View File

@@ -2,7 +2,6 @@ package obfuscated2
2 2
 
3 3
 import (
4 4
 	"crypto/sha256"
5
-	"fmt"
6 5
 	"testing"
7 6
 
8 7
 	"github.com/stretchr/testify/assert"

+ 11
- 6
proxy/cipherrwc.go View File

@@ -5,39 +5,44 @@ import (
5 5
 	"io"
6 6
 )
7 7
 
8
+// Cipher is an interface to anything which can encrypt and decrypt
8 9
 type Cipher interface {
9 10
 	Encrypt([]byte) []byte
10 11
 	Decrypt([]byte) []byte
11 12
 }
12 13
 
14
+// CipherReadWriteCloser wraps connection for transparent encryption
13 15
 type CipherReadWriteCloser struct {
14 16
 	crypt Cipher
15 17
 	conn  io.ReadWriteCloser
16 18
 	rest  *bytes.Buffer
17 19
 }
18 20
 
21
+// Read reads from connection
19 22
 func (c *CipherReadWriteCloser) Read(p []byte) (n int, err error) {
20 23
 	n, err = c.conn.Read(p)
21 24
 	copy(p, c.crypt.Decrypt(p[:n]))
22 25
 	return
23 26
 }
24 27
 
25
-func (c *CipherReadWriteCloser) Write(p []byte) (n int, err error) {
28
+// Write writes into connection.
29
+func (c *CipherReadWriteCloser) Write(p []byte) (int, error) {
26 30
 	encrypted := c.crypt.Encrypt(p)
31
+	allWritten := 0
27 32
 
28
-	curN := 0
29 33
 	for len(encrypted) > 0 {
30
-		curN, err = c.conn.Write(encrypted)
31
-		n += curN
34
+		n, err := c.conn.Write(encrypted)
35
+		allWritten += n
32 36
 		if err != nil {
33
-			return
37
+			return allWritten, err
34 38
 		}
35 39
 		encrypted = encrypted[n:]
36 40
 	}
37 41
 
38
-	return
42
+	return allWritten, nil
39 43
 }
40 44
 
45
+// Close closes underlying connection.
41 46
 func (c *CipherReadWriteCloser) Close() error {
42 47
 	return c.conn.Close()
43 48
 }

+ 6
- 1
proxy/ctxrwc.go View File

@@ -7,12 +7,15 @@ import (
7 7
 	"github.com/juju/errors"
8 8
 )
9 9
 
10
+// CtxReadWriteCloser wraps underlying connection and does management of the
11
+// context and its cancel function.
10 12
 type CtxReadWriteCloser struct {
11 13
 	ctx    context.Context
12 14
 	conn   io.ReadWriteCloser
13 15
 	cancel context.CancelFunc
14 16
 }
15 17
 
18
+// Read reads from connection
16 19
 func (c *CtxReadWriteCloser) Read(p []byte) (int, error) {
17 20
 	select {
18 21
 	case <-c.ctx.Done():
@@ -26,6 +29,7 @@ func (c *CtxReadWriteCloser) Read(p []byte) (int, error) {
26 29
 	}
27 30
 }
28 31
 
32
+// Write writes into connection.
29 33
 func (c *CtxReadWriteCloser) Write(p []byte) (int, error) {
30 34
 	select {
31 35
 	case <-c.ctx.Done():
@@ -39,11 +43,12 @@ func (c *CtxReadWriteCloser) Write(p []byte) (int, error) {
39 43
 	}
40 44
 }
41 45
 
46
+// Close closes underlying connection.
42 47
 func (c *CtxReadWriteCloser) Close() error {
43 48
 	return c.conn.Close()
44 49
 }
45 50
 
46
-func newCtxReadWriteCloser(conn io.ReadWriteCloser, ctx context.Context, cancel context.CancelFunc) io.ReadWriteCloser {
51
+func newCtxReadWriteCloser(ctx context.Context, cancel context.CancelFunc, conn io.ReadWriteCloser) io.ReadWriteCloser {
47 52
 	return &CtxReadWriteCloser{
48 53
 		conn:   conn,
49 54
 		ctx:    ctx,

+ 5
- 0
proxy/logrwc.go View File

@@ -6,6 +6,8 @@ import (
6 6
 	"go.uber.org/zap"
7 7
 )
8 8
 
9
+// LogReadWriteCloser adds additional logging for reading/writing. All
10
+// logging is performed for debug mode only.
9 11
 type LogReadWriteCloser struct {
10 12
 	conn   io.ReadWriteCloser
11 13
 	logger *zap.SugaredLogger
@@ -13,18 +15,21 @@ type LogReadWriteCloser struct {
13 15
 	name   string
14 16
 }
15 17
 
18
+// Read reads from connection
16 19
 func (l *LogReadWriteCloser) Read(p []byte) (n int, err error) {
17 20
 	n, err = l.conn.Read(p)
18 21
 	l.logger.Debugw("Finish reading", "name", l.name, "socketid", l.sockid, "nbytes", n, "error", err)
19 22
 	return
20 23
 }
21 24
 
25
+// Write writes into connection.
22 26
 func (l *LogReadWriteCloser) Write(p []byte) (n int, err error) {
23 27
 	n, err = l.conn.Write(p)
24 28
 	l.logger.Debugw("Finish writing", "name", l.name, "socketid", l.sockid, "nbytes", n, "error", err)
25 29
 	return
26 30
 }
27 31
 
32
+// Close closes underlying connection.
28 33
 func (l *LogReadWriteCloser) Close() error {
29 34
 	err := l.conn.Close()
30 35
 	l.logger.Debugw("Finish closing socket", "name", l.name, "socketid", l.sockid, "error", err)

+ 22
- 26
proxy/server.go View File

@@ -14,14 +14,12 @@ import (
14 14
 	"go.uber.org/zap"
15 15
 )
16 16
 
17
-const bufferSize = 4096
18
-
17
+// Server is an insgtance of MTPROTO proxy.
19 18
 type Server struct {
20 19
 	ip           net.IP
21 20
 	port         int
22 21
 	secret       []byte
23 22
 	logger       *zap.SugaredLogger
24
-	lsock        net.Listener
25 23
 	ctx          context.Context
26 24
 	readTimeout  time.Duration
27 25
 	writeTimeout time.Duration
@@ -29,8 +27,10 @@ type Server struct {
29 27
 	ipv6         bool
30 28
 }
31 29
 
30
+// Serve does MTPROTO proxying.
32 31
 func (s *Server) Serve() error {
33
-	lsock, err := net.Listen("tcp", s.Addr())
32
+	addr := net.JoinHostPort(s.ip.String(), strconv.Itoa(s.port))
33
+	lsock, err := net.Listen("tcp", addr)
34 34
 	if err != nil {
35 35
 		return errors.Annotate(err, "Cannot create listen socket")
36 36
 	}
@@ -42,18 +42,12 @@ func (s *Server) Serve() error {
42 42
 			go s.accept(conn)
43 43
 		}
44 44
 	}
45
-
46
-	return nil
47
-}
48
-
49
-func (s *Server) Addr() string {
50
-	return net.JoinHostPort(s.ip.String(), strconv.Itoa(s.port))
51 45
 }
52 46
 
53 47
 func (s *Server) accept(conn net.Conn) {
54 48
 	defer func() {
55 49
 		s.stats.closeConnection()
56
-		conn.Close()
50
+		conn.Close() // nolint: errcheck
57 51
 
58 52
 		if r := recover(); r != nil {
59 53
 			s.logger.Errorw("Crash of accept handler", "error", r)
@@ -70,7 +64,7 @@ func (s *Server) accept(conn net.Conn) {
70 64
 		"socketid", socketID,
71 65
 	)
72 66
 
73
-	clientConn, dc, err := s.getClientStream(conn, ctx, cancel, socketID)
67
+	clientConn, dc, err := s.getClientStream(ctx, cancel, conn, socketID)
74 68
 	if err != nil {
75 69
 		s.logger.Warnw("Cannot initialize client connection",
76 70
 			"secret", s.secret,
@@ -80,9 +74,9 @@ func (s *Server) accept(conn net.Conn) {
80 74
 		)
81 75
 		return
82 76
 	}
83
-	defer clientConn.Close()
77
+	defer clientConn.Close() // nolint: errcheck
84 78
 
85
-	tgConn, err := s.getTelegramStream(dc, ctx, cancel, socketID)
79
+	tgConn, err := s.getTelegramStream(ctx, cancel, dc, socketID)
86 80
 	if err != nil {
87 81
 		s.logger.Warnw("Cannot initialize Telegram connection",
88 82
 			"socketid", socketID,
@@ -90,12 +84,18 @@ func (s *Server) accept(conn net.Conn) {
90 84
 		)
91 85
 		return
92 86
 	}
93
-	defer tgConn.Close()
87
+	defer tgConn.Close() // nolint: errcheck
94 88
 
95 89
 	wait := &sync.WaitGroup{}
96 90
 	wait.Add(2)
97
-	go s.pipe(wait, clientConn, tgConn)
98
-	go s.pipe(wait, tgConn, clientConn)
91
+	go func() {
92
+		defer wait.Done()
93
+		io.Copy(clientConn, tgConn) // nolint: errcheck
94
+	}()
95
+	go func() {
96
+		defer wait.Done()
97
+		io.Copy(tgConn, clientConn) // nolint: errcheck
98
+	}()
99 99
 	<-ctx.Done()
100 100
 	wait.Wait()
101 101
 
@@ -110,7 +110,7 @@ func (s *Server) makeSocketID() string {
110 110
 	return uuid.NewV4().String()
111 111
 }
112 112
 
113
-func (s *Server) getClientStream(conn net.Conn, ctx context.Context, cancel context.CancelFunc, socketID string) (io.ReadWriteCloser, int16, error) {
113
+func (s *Server) getClientStream(ctx context.Context, cancel context.CancelFunc, conn net.Conn, socketID string) (io.ReadWriteCloser, int16, error) {
114 114
 	wConn := newTimeoutReadWriteCloser(conn, s.readTimeout, s.writeTimeout)
115 115
 	wConn = newTrafficReadWriteCloser(wConn, s.stats.addIncomingTraffic, s.stats.addOutgoingTraffic)
116 116
 	frame, err := obfuscated2.ExtractFrame(wConn)
@@ -125,12 +125,12 @@ func (s *Server) getClientStream(conn net.Conn, ctx context.Context, cancel cont
125 125
 
126 126
 	wConn = newLogReadWriteCloser(wConn, s.logger, socketID, "client")
127 127
 	wConn = newCipherReadWriteCloser(wConn, obfs2)
128
-	wConn = newCtxReadWriteCloser(wConn, ctx, cancel)
128
+	wConn = newCtxReadWriteCloser(ctx, cancel, wConn)
129 129
 
130 130
 	return wConn, dc, nil
131 131
 }
132 132
 
133
-func (s *Server) getTelegramStream(dc int16, ctx context.Context, cancel context.CancelFunc, socketID string) (io.ReadWriteCloser, error) {
133
+func (s *Server) getTelegramStream(ctx context.Context, cancel context.CancelFunc, dc int16, socketID string) (io.ReadWriteCloser, error) {
134 134
 	socket, err := dialToTelegram(s.ipv6, dc, s.readTimeout)
135 135
 	if err != nil {
136 136
 		return nil, errors.Annotate(err, "Cannot dial")
@@ -145,16 +145,12 @@ func (s *Server) getTelegramStream(dc int16, ctx context.Context, cancel context
145 145
 
146 146
 	wConn = newLogReadWriteCloser(wConn, s.logger, socketID, "telegram")
147 147
 	wConn = newCipherReadWriteCloser(wConn, obfs2)
148
-	wConn = newCtxReadWriteCloser(wConn, ctx, cancel)
148
+	wConn = newCtxReadWriteCloser(ctx, cancel, wConn)
149 149
 
150 150
 	return wConn, nil
151 151
 }
152 152
 
153
-func (s *Server) pipe(wait *sync.WaitGroup, reader io.Reader, writer io.Writer) {
154
-	defer wait.Done()
155
-	io.Copy(writer, reader)
156
-}
157
-
153
+// NewServer creates new instance of MTPROTO proxy.
158 154
 func NewServer(ip net.IP, port int, secret []byte, logger *zap.SugaredLogger,
159 155
 	readTimeout, writeTimeout time.Duration, ipv6 bool, stat *Stats) *Server {
160 156
 	return &Server{

+ 7
- 3
proxy/stats.go View File

@@ -2,6 +2,7 @@ package proxy
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"fmt"
5 6
 	"net"
6 7
 	"net/http"
7 8
 	"net/url"
@@ -17,6 +18,7 @@ func (s statsUptime) MarshalJSON() ([]byte, error) {
17 18
 	return []byte(strconv.Itoa(uptime)), nil
18 19
 }
19 20
 
21
+// Stats is a datastructure for statistics on work of this proxy.
20 22
 type Stats struct {
21 23
 	AllConnections    uint64 `json:"all_connections"`
22 24
 	ActiveConnections uint32 `json:"active_connections"`
@@ -50,20 +52,22 @@ func (s *Stats) addOutgoingTraffic(n int) {
50 52
 	atomic.AddUint64(&s.Traffic.Outgoing, uint64(n))
51 53
 }
52 54
 
53
-func (s *Stats) Serve(host net.IP, port uint16) {
55
+// Serve runs statistics HTTP server.
56
+func (s *Stats) Serve(host fmt.Stringer, port uint16) {
54 57
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
55 58
 		w.Header().Set("Content-Type", "application/json")
56 59
 
57 60
 		encoder := json.NewEncoder(w)
58 61
 		encoder.SetEscapeHTML(false)
59 62
 		encoder.SetIndent("", "  ")
60
-		encoder.Encode(s)
63
+		encoder.Encode(s) // nolint: errcheck, gas
61 64
 	})
62 65
 
63 66
 	addr := net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
64
-	http.ListenAndServe(addr, nil)
67
+	http.ListenAndServe(addr, nil) // nolint: errcheck, gas
65 68
 }
66 69
 
70
+// NewStats returns new instance of statistics datastructure.
67 71
 func NewStats(serverName string, port uint16, secret string) *Stats {
68 72
 	urlQuery := makeURLQuery(serverName, port, secret)
69 73
 

+ 5
- 0
proxy/telegram.go View File

@@ -7,19 +7,24 @@ import (
7 7
 	"github.com/juju/errors"
8 8
 )
9 9
 
10
+// TelegramAddress presents a pair of v4 and v6 addresses. This pairization
11
+// is required because we want to use DC indexes.
10 12
 type TelegramAddress struct {
11 13
 	v4 string
12 14
 	v6 string
13 15
 }
14 16
 
17
+// IPv4 returns v4 address.
15 18
 func (t *TelegramAddress) IPv4() string {
16 19
 	return net.JoinHostPort(t.v4, telegramPort)
17 20
 }
18 21
 
22
+// IPv6 returns v4 address.
19 23
 func (t *TelegramAddress) IPv6() string {
20 24
 	return net.JoinHostPort(t.v6, telegramPort)
21 25
 }
22 26
 
27
+// TelegramAddresses is a list of all known Telegram addresses for DC indexes.
23 28
 var TelegramAddresses = []TelegramAddress{
24 29
 	TelegramAddress{v4: "149.154.175.50", v6: "2001:b28:f23d:f001::a"},
25 30
 	TelegramAddress{v4: "149.154.167.51", v6: "2001:67c:04e8:f002::a"},

+ 7
- 2
proxy/timeoutrwc.go View File

@@ -6,22 +6,27 @@ import (
6 6
 	"time"
7 7
 )
8 8
 
9
+// TimeoutReadWriteCloser sets timeouts for read/write into underlying
10
+// network connection.
9 11
 type TimeoutReadWriteCloser struct {
10 12
 	conn         net.Conn
11 13
 	readTimeout  time.Duration
12 14
 	writeTimeout time.Duration
13 15
 }
14 16
 
17
+// Read reads from connection
15 18
 func (t *TimeoutReadWriteCloser) Read(p []byte) (int, error) {
16
-	t.conn.SetReadDeadline(time.Now().Add(t.readTimeout))
19
+	t.conn.SetReadDeadline(time.Now().Add(t.readTimeout)) // nolint: errcheck, gas
17 20
 	return t.conn.Read(p)
18 21
 }
19 22
 
23
+// Write writes into connection.
20 24
 func (t *TimeoutReadWriteCloser) Write(p []byte) (int, error) {
21
-	t.conn.SetWriteDeadline(time.Now().Add(t.writeTimeout))
25
+	t.conn.SetWriteDeadline(time.Now().Add(t.writeTimeout)) // nolint: errcheck, gas
22 26
 	return t.conn.Write(p)
23 27
 }
24 28
 
29
+// Close closes underlying connection.
25 30
 func (t *TimeoutReadWriteCloser) Close() error {
26 31
 	return t.conn.Close()
27 32
 }

+ 5
- 0
proxy/trafficrwc.go View File

@@ -2,24 +2,29 @@ package proxy
2 2
 
3 3
 import "io"
4 4
 
5
+// TrafficReadWriteCloser counts an amount of ingress/egress traffic by
6
+// calling given callbacks.
5 7
 type TrafficReadWriteCloser struct {
6 8
 	conn          io.ReadWriteCloser
7 9
 	readCallback  func(int)
8 10
 	writeCallback func(int)
9 11
 }
10 12
 
13
+// Read reads from connection
11 14
 func (t *TrafficReadWriteCloser) Read(p []byte) (n int, err error) {
12 15
 	n, err = t.conn.Read(p)
13 16
 	t.readCallback(n)
14 17
 	return
15 18
 }
16 19
 
20
+// Write writes into connection.
17 21
 func (t *TrafficReadWriteCloser) Write(p []byte) (n int, err error) {
18 22
 	n, err = t.conn.Write(p)
19 23
 	t.writeCallback(n)
20 24
 	return
21 25
 }
22 26
 
27
+// Close closes underlying connection.
23 28
 func (t *TrafficReadWriteCloser) Close() error {
24 29
 	return t.conn.Close()
25 30
 }

Loading…
Cancel
Save