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 }