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  }