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  }