github.com/lmb/consul@v1.4.1/connect/proxy/conn.go (about) 1 package proxy 2 3 import ( 4 "io" 5 "net" 6 "sync/atomic" 7 ) 8 9 // Conn represents a single proxied TCP connection. 10 type Conn struct { 11 src, dst net.Conn 12 // TODO(banks): benchmark and consider adding _ [8]uint64 padding between 13 // these to prevent false sharing between the rx and tx goroutines when 14 // running on separate cores. 15 srcW, dstW countWriter 16 stopping int32 17 } 18 19 // NewConn returns a conn joining the two given net.Conn 20 func NewConn(src, dst net.Conn) *Conn { 21 return &Conn{ 22 src: src, 23 dst: dst, 24 srcW: countWriter{w: src}, 25 dstW: countWriter{w: dst}, 26 stopping: 0, 27 } 28 } 29 30 // Close closes both connections. 31 func (c *Conn) Close() error { 32 // Note that net.Conn.Close can be called multiple times and atomic store is 33 // idempotent so no need to ensure we only do this once. 34 // 35 // Also note that we don't wait for CopyBytes to return here since we are 36 // closing the conns which is the only externally visible sideeffect of that 37 // goroutine running and there should be no way for it to hang or leak once 38 // the conns are closed so we can save the extra coordination. 39 atomic.StoreInt32(&c.stopping, 1) 40 c.src.Close() 41 c.dst.Close() 42 return nil 43 } 44 45 // CopyBytes will continuously copy bytes in both directions between src and dst 46 // until either connection is closed. 47 func (c *Conn) CopyBytes() error { 48 defer c.Close() 49 50 go func() { 51 // Need this since Copy is only guaranteed to stop when it's source reader 52 // (second arg) hits EOF or error but either conn might close first possibly 53 // causing this goroutine to exit but not the outer one. See 54 // TestConnSrcClosing which will fail if you comment the defer below. 55 defer c.Close() 56 io.Copy(&c.dstW, c.src) 57 }() 58 59 _, err := io.Copy(&c.srcW, c.dst) 60 // Note that we don't wait for the other goroutine to finish because it either 61 // already has due to it's src conn closing, or it will once our defer fires 62 // and closes the source conn. No need for the extra coordination. 63 if atomic.LoadInt32(&c.stopping) == 1 { 64 return nil 65 } 66 return err 67 } 68 69 // Stats returns number of bytes transmitted and recieved. Transmit means bytes 70 // written to dst, receive means bytes written to src. 71 func (c *Conn) Stats() (txBytes, rxBytes uint64) { 72 return c.srcW.Written(), c.dstW.Written() 73 } 74 75 // countWriter is an io.Writer that counts the number of bytes being written 76 // before passing them through. We use it to gather metrics for bytes 77 // sent/received. Note that since we are always copying between a net.TCPConn 78 // and a tls.Conn, none of the optimisations using syscalls like splice and 79 // ReaderTo/WriterFrom can be used anyway and io.Copy falls back to a generic 80 // buffered read/write loop. 81 // 82 // We use atomic updates to synchronize reads and writes here. It's the cheapest 83 // uncontended option based on 84 // https://gist.github.com/banks/e76b40c0cc4b01503f0a0e4e0af231d5. Further 85 // optimization can be made when if/when identified as a real overhead. 86 type countWriter struct { 87 written uint64 88 w io.Writer 89 } 90 91 // Write implements io.Writer 92 func (cw *countWriter) Write(p []byte) (n int, err error) { 93 n, err = cw.w.Write(p) 94 atomic.AddUint64(&cw.written, uint64(n)) 95 return 96 } 97 98 // Written returns how many bytes have been written to w. 99 func (cw *countWriter) Written() uint64 { 100 return atomic.LoadUint64(&cw.written) 101 }