github.com/google/cloudprober@v0.11.3/servers/udp/udp.go (about) 1 // Copyright 2017-2020 The Cloudprober Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /* 16 Package udp implements a UDP server. It listens on a 17 given port and echos whatever it receives. This is used for the UDP probe. 18 */ 19 package udp 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "net" 26 "runtime" 27 "strings" 28 29 "github.com/google/cloudprober/logger" 30 "github.com/google/cloudprober/metrics" 31 configpb "github.com/google/cloudprober/servers/udp/proto" 32 "golang.org/x/net/ipv6" 33 ) 34 35 const ( 36 // recv socket buffer size - we want to use a large value here, preferably 37 // the maximum allowed by the OS. 425984 is the max value in 38 // Container-Optimized OS version 9592.90.0. 39 readBufSize = 425984 40 41 // Number of messages to batch read. 42 batchSize = 16 43 44 // Maximum packet size. 45 // TODO(manugarg): We read and echo back only 4098 bytes. We should look at raising this 46 // limit or making it configurable. Also of note, ReadFromUDP reads a single UDP datagram 47 // (up to the max size of 64K-sizeof(UDPHdr)) and discards the rest. 48 maxPacketSize = 4098 49 ) 50 51 // Server implements a basic UDP server. 52 type Server struct { 53 c *configpb.ServerConf 54 conn *net.UDPConn 55 l *logger.Logger 56 57 advancedReadWrite bool // Set to true on non-windows systems 58 p6 *ipv6.PacketConn 59 } 60 61 // New returns an UDP server. 62 func New(initCtx context.Context, c *configpb.ServerConf, l *logger.Logger) (*Server, error) { 63 conn, err := Listen(&net.UDPAddr{Port: int(c.GetPort())}, l) 64 if err != nil { 65 return nil, err 66 } 67 go func() { 68 <-initCtx.Done() 69 conn.Close() 70 }() 71 72 s := &Server{ 73 c: c, 74 conn: conn, 75 l: l, 76 } 77 78 switch runtime.GOOS { 79 case "windows": 80 // Control messages are not supported. 81 default: 82 s.advancedReadWrite = true 83 // We use an IPv6 connection wrapper to receive both IPv4 and IPv6 packets. 84 // ipv6.PacketConn lets us use control messages (non-Windows only) to: 85 // -- receive packet destination IP (FlagDst) 86 // -- set source IP (Src). 87 s.p6 = ipv6.NewPacketConn(conn) 88 if err := s.p6.SetControlMessage(ipv6.FlagDst, true); err != nil { 89 return nil, fmt.Errorf("SetControlMessage(ipv6.FlagDst, true) failed: %v", err) 90 } 91 } 92 93 return s, nil 94 } 95 96 // Listen opens a UDP socket on the given port. It also attempts to set recv 97 // buffer to a large value so that we can have many outstanding UDP messages. 98 // Listen is exported only because it's used by udp probe tests. 99 func Listen(addr *net.UDPAddr, l *logger.Logger) (*net.UDPConn, error) { 100 conn, err := net.ListenUDP("udp", addr) 101 if err != nil { 102 return nil, err 103 } 104 if err = conn.SetReadBuffer(readBufSize); err != nil { 105 // Non-fatal error if we are not able to set read socket buffer. 106 l.Errorf("Error setting UDP socket %v read buffer to %d: %s. Continuing...", 107 conn.LocalAddr(), readBufSize, err) 108 } 109 110 return conn, nil 111 } 112 113 // readWriteErr is used by readAndEcho functions so that we can return both, 114 // the relevant error message and the original error. Original error is used 115 // to determine if the underlying transport has been closed. 116 type readWriteErr struct { 117 msg string 118 err error 119 } 120 121 func (rwerr *readWriteErr) Error() string { 122 if rwerr.err != nil { 123 return fmt.Sprintf("%s: %v", rwerr.msg, rwerr.err) 124 } 125 return rwerr.msg 126 } 127 128 // readAndEchoBatch reads, writes packets in batches. To determine the source 129 // address for outgoing packets (e.g. if server is behind a load balancer), we 130 // make use of control messages (configured through messages' OOB). 131 // 132 // Note that we don't need to copy or modify the messages below before echoing 133 // them for the following reasons: 134 // - Message struct uses the same field (Addr) for sender (while receiving) 135 // and destination address (while sending). 136 // - Control message (type: packet-info) field that contains the received 137 // packet's destination address, is also the field that's used to set the 138 // source address on the outgoing packets. 139 func (s *Server) readAndEchoBatch(ms []ipv6.Message) *readWriteErr { 140 n, err := s.p6.ReadBatch(ms, 0) 141 if err != nil { 142 return &readWriteErr{"error reading packets", err} 143 } 144 ms = ms[:n] 145 146 // Resize buffers to match amount read. 147 for _, m := range ms { 148 // We only allocated a 0th buffer, so all the data is there. 149 m.Buffers[0] = m.Buffers[0][:m.N] 150 } 151 152 for remaining := len(ms); remaining > 0; { 153 n, err := s.p6.WriteBatch(ms, 0) 154 if err != nil { 155 return &readWriteErr{"error writing packets", err} 156 } 157 if n == 0 { 158 return &readWriteErr{fmt.Sprintf("wrote zero packets, %d remain", remaining), nil} 159 } 160 remaining -= n 161 } 162 163 // Reset buffers to full size for re-use. 164 for _, m := range ms { 165 b := m.Buffers[0] 166 // We only allocated a 0th buffer. 167 m.Buffers[0] = b[:cap(b)] 168 } 169 170 return nil 171 } 172 173 // readAndEchoSimple reads a packet from the server connection and writes it 174 // back. 175 func (s *Server) readAndEchoSimple(buf []byte) *readWriteErr { 176 inLen, addr, err := s.conn.ReadFromUDP(buf) 177 if err != nil { 178 return &readWriteErr{"error reading packet", err} 179 } 180 if inLen == 0 { 181 return &readWriteErr{"read 0 length packet", nil} 182 } 183 184 n, err := s.conn.WriteToUDP(buf[:inLen], addr) 185 if err != nil { 186 return &readWriteErr{"error writing packet", err} 187 } 188 189 if n < inLen { 190 s.l.Warningf("Reply truncated! Got %d bytes but only sent %d bytes", inLen, n) 191 } 192 return nil 193 } 194 195 func connClosed(err error) bool { 196 // TODO(manugarg): Replace this by errors.Is(err, net.ErrClosed) once Go 1.16 197 // is more widely available. 198 return strings.Contains(err.Error(), "use of closed network connection") 199 } 200 201 // Start starts the UDP server. It returns only when context is canceled. 202 func (s *Server) Start(ctx context.Context, dataChan chan<- *metrics.EventMetrics) error { 203 var ms []ipv6.Message // Used for batch read-write 204 buf := make([]byte, maxPacketSize) // Used for single packet read-write (windows) 205 206 if s.advancedReadWrite { 207 ms = make([]ipv6.Message, batchSize) 208 for i := 0; i < batchSize; i++ { 209 ms[i].Buffers = [][]byte{make([]byte, maxPacketSize)} 210 ms[i].OOB = ipv6.NewControlMessage(ipv6.FlagDst) 211 } 212 } 213 214 // Setup a background function to close connection if context is canceled. 215 // Typically, this is not what we want (close something started outside of 216 // Start function), but in case of UDP we don't have better control than 217 // this. One thing we can consider is to re-setup connection in Start(). 218 go func() { 219 <-ctx.Done() 220 s.conn.Close() 221 }() 222 223 switch s.c.GetType() { 224 225 case configpb.ServerConf_ECHO: 226 s.l.Infof("Starting UDP ECHO server on port %d", int(s.c.GetPort())) 227 228 var rwerr *readWriteErr 229 for { 230 if s.advancedReadWrite { 231 rwerr = s.readAndEchoBatch(ms) 232 } else { 233 rwerr = s.readAndEchoSimple(buf) 234 } 235 if rwerr != nil { 236 if errors.Is(rwerr.err, net.ErrClosed) { 237 s.l.Warning("connection closed, stopping the start goroutine") 238 return nil 239 } 240 s.l.Error(rwerr.Error()) 241 } 242 } 243 244 case configpb.ServerConf_DISCARD: 245 s.l.Infof("Starting UDP DISCARD server on port %d", int(s.c.GetPort())) 246 247 var err error 248 for { 249 if s.advancedReadWrite { 250 _, err = s.p6.ReadBatch(ms, 0) 251 } else { 252 _, _, err = s.conn.ReadFromUDP(buf) 253 } 254 255 if err != nil { 256 if errors.Is(err, net.ErrClosed) { 257 return nil 258 } 259 s.l.Errorf("ReadFromUDP: %v", err) 260 } 261 } 262 } 263 264 return nil 265 }