github.com/uber-go/tally/v4@v4.1.17/m3/thriftudp/transport.go (about)

     1  // Copyright (c) 2021 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package thriftudp
    22  
    23  import (
    24  	"bytes"
    25  	"context"
    26  	"net"
    27  
    28  	"github.com/uber-go/tally/v4/thirdparty/github.com/apache/thrift/lib/go/thrift"
    29  	"go.uber.org/atomic"
    30  )
    31  
    32  // MaxLength of UDP packet
    33  const MaxLength = 65000
    34  
    35  // Ensure TUDPTransport implements TRichTransport which avoids allocations
    36  // when writing strings.
    37  var _ thrift.TRichTransport = (*TUDPTransport)(nil)
    38  
    39  // TUDPTransport does UDP as a thrift.TTransport
    40  type TUDPTransport struct {
    41  	conn        *net.UDPConn
    42  	addr        net.Addr
    43  	writeBuf    bytes.Buffer
    44  	readByteBuf []byte
    45  	closed      atomic.Bool
    46  }
    47  
    48  // NewTUDPClientTransport creates a net.UDPConn-backed TTransport for Thrift clients
    49  // All writes are buffered and flushed in one UDP packet. If locHostPort is not "", it
    50  // will be used as the local address for the connection
    51  // Example:
    52  // 	trans, err := thriftudp.NewTUDPClientTransport("192.168.1.1:9090", "")
    53  func NewTUDPClientTransport(destHostPort string, locHostPort string) (*TUDPTransport, error) {
    54  	destAddr, err := net.ResolveUDPAddr("udp", destHostPort)
    55  	if err != nil {
    56  		return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error())
    57  	}
    58  
    59  	var locAddr *net.UDPAddr
    60  	if locHostPort != "" {
    61  		locAddr, err = net.ResolveUDPAddr("udp", locHostPort)
    62  		if err != nil {
    63  			return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error())
    64  		}
    65  	}
    66  
    67  	conn, err := net.DialUDP(destAddr.Network(), locAddr, destAddr)
    68  	if err != nil {
    69  		return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error())
    70  	}
    71  
    72  	return &TUDPTransport{
    73  		addr:        destAddr,
    74  		conn:        conn,
    75  		readByteBuf: make([]byte, 1),
    76  	}, nil
    77  }
    78  
    79  // NewTUDPServerTransport creates a net.UDPConn-backed TTransport for Thrift servers
    80  // It will listen for incoming udp packets on the specified host/port
    81  // Example:
    82  // 	trans, err := thriftudp.NewTUDPClientTransport("localhost:9001")
    83  func NewTUDPServerTransport(hostPort string) (*TUDPTransport, error) {
    84  	return NewTUDPServerTransportWithListenConfig(hostPort, net.ListenConfig{})
    85  }
    86  
    87  // NewTUDPServerTransportWithListenConfig creates a net.UDPConn-backed TTransport for Thrift servers
    88  // It will listen for incoming udp packets on the specified host/port
    89  // It takes net.ListenConfig to customize socket options
    90  func NewTUDPServerTransportWithListenConfig(
    91  	hostPort string,
    92  	listenConfig net.ListenConfig,
    93  ) (*TUDPTransport, error) {
    94  	conn, err := listenConfig.ListenPacket(context.Background(), "udp", hostPort)
    95  	if err != nil {
    96  		return nil, thrift.NewTTransportException(thrift.NOT_OPEN, err.Error())
    97  	}
    98  
    99  	uconn, ok := conn.(*net.UDPConn)
   100  	if !ok {
   101  		return nil, thrift.NewTTransportException(thrift.NOT_OPEN, "not a udp connection")
   102  	}
   103  
   104  	return &TUDPTransport{
   105  		addr:        uconn.LocalAddr(),
   106  		conn:        uconn,
   107  		readByteBuf: make([]byte, 1),
   108  	}, nil
   109  }
   110  
   111  // Open does nothing as connection is opened on creation
   112  // Required to maintain thrift.TTransport interface
   113  func (p *TUDPTransport) Open() error {
   114  	return nil
   115  }
   116  
   117  // Conn retrieves the underlying net.UDPConn
   118  func (p *TUDPTransport) Conn() *net.UDPConn {
   119  	return p.conn
   120  }
   121  
   122  // IsOpen returns true if the connection is open
   123  func (p *TUDPTransport) IsOpen() bool {
   124  	return !p.closed.Load()
   125  }
   126  
   127  // Close closes the transport and the underlying connection.
   128  // Note: the current implementation allows Close to be called multiple times without an error.
   129  func (p *TUDPTransport) Close() error {
   130  	if closed := p.closed.Swap(true); !closed {
   131  		return p.conn.Close()
   132  	}
   133  	return nil
   134  }
   135  
   136  // Addr returns the address that the transport is listening on or writing to
   137  func (p *TUDPTransport) Addr() net.Addr {
   138  	return p.addr
   139  }
   140  
   141  // Read reads one UDP packet and puts it in the specified buf
   142  func (p *TUDPTransport) Read(buf []byte) (int, error) {
   143  	if !p.IsOpen() {
   144  		return 0, thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open")
   145  	}
   146  	n, err := p.conn.Read(buf)
   147  	return n, thrift.NewTTransportExceptionFromError(err)
   148  }
   149  
   150  // ReadByte reads a single byte and returns it
   151  func (p *TUDPTransport) ReadByte() (byte, error) {
   152  	n, err := p.Read(p.readByteBuf)
   153  	if err != nil {
   154  		return 0, err
   155  	}
   156  
   157  	if n == 0 {
   158  		return 0, thrift.NewTTransportException(thrift.PROTOCOL_ERROR, "Received empty packet")
   159  	}
   160  
   161  	return p.readByteBuf[0], nil
   162  }
   163  
   164  // RemainingBytes returns the max number of bytes (same as Thrift's StreamTransport) as we
   165  // do not know how many bytes we have left.
   166  func (p *TUDPTransport) RemainingBytes() uint64 {
   167  	const maxSize = ^uint64(0)
   168  	return maxSize
   169  }
   170  
   171  // Write writes specified buf to the write buffer
   172  func (p *TUDPTransport) Write(buf []byte) (int, error) {
   173  	if !p.IsOpen() {
   174  		return 0, thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open")
   175  	}
   176  	if p.writeBuf.Len()+len(buf) > MaxLength {
   177  		return 0, thrift.NewTTransportException(thrift.INVALID_DATA, "Data does not fit within one UDP packet")
   178  	}
   179  	n, err := p.writeBuf.Write(buf)
   180  	return n, thrift.NewTTransportExceptionFromError(err)
   181  }
   182  
   183  // WriteByte writes a single byte to the write buffer
   184  func (p *TUDPTransport) WriteByte(b byte) error {
   185  	if !p.IsOpen() {
   186  		return thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open")
   187  	}
   188  	if p.writeBuf.Len()+1 > MaxLength {
   189  		return thrift.NewTTransportException(thrift.INVALID_DATA, "Data does not fit within one UDP packet")
   190  	}
   191  
   192  	err := p.writeBuf.WriteByte(b)
   193  	return thrift.NewTTransportExceptionFromError(err)
   194  }
   195  
   196  // WriteString writes the specified string to the write buffer
   197  func (p *TUDPTransport) WriteString(s string) (int, error) {
   198  	if !p.IsOpen() {
   199  		return 0, thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open")
   200  	}
   201  	if p.writeBuf.Len()+len(s) > MaxLength {
   202  		return 0, thrift.NewTTransportException(thrift.INVALID_DATA, "Data does not fit within one UDP packet")
   203  	}
   204  
   205  	n, err := p.writeBuf.WriteString(s)
   206  	return n, thrift.NewTTransportExceptionFromError(err)
   207  }
   208  
   209  // Flush flushes the write buffer as one udp packet
   210  func (p *TUDPTransport) Flush() error {
   211  	if !p.IsOpen() {
   212  		return thrift.NewTTransportException(thrift.NOT_OPEN, "Connection not open")
   213  	}
   214  
   215  	_, err := p.conn.Write(p.writeBuf.Bytes())
   216  	p.writeBuf.Reset() // always reset the buffer, even in case of an error
   217  	return err
   218  }