github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/port/builtin/parent/udp/udpproxy/udp_proxy.go (about)

     1  // Package udpproxy is from https://raw.githubusercontent.com/docker/libnetwork/fec6476dfa21380bf8ee4d74048515d968c1ee63/cmd/proxy/udp_proxy.go
     2  package udpproxy
     3  
     4  import (
     5  	"encoding/binary"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"strings"
    10  	"sync"
    11  	"syscall"
    12  	"time"
    13  )
    14  
    15  const (
    16  	// UDPConnTrackTimeout is the timeout used for UDP connection tracking
    17  	UDPConnTrackTimeout = 90 * time.Second
    18  	// UDPBufSize is the buffer size for the UDP proxy
    19  	UDPBufSize = 65507
    20  )
    21  
    22  // A net.Addr where the IP is split into two fields so you can use it as a key
    23  // in a map:
    24  type connTrackKey struct {
    25  	IPHigh uint64
    26  	IPLow  uint64
    27  	Port   int
    28  }
    29  
    30  func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
    31  	if len(addr.IP) == net.IPv4len {
    32  		return &connTrackKey{
    33  			IPHigh: 0,
    34  			IPLow:  uint64(binary.BigEndian.Uint32(addr.IP)),
    35  			Port:   addr.Port,
    36  		}
    37  	}
    38  	return &connTrackKey{
    39  		IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
    40  		IPLow:  binary.BigEndian.Uint64(addr.IP[8:]),
    41  		Port:   addr.Port,
    42  	}
    43  }
    44  
    45  type connTrackMap map[connTrackKey]*net.UDPConn
    46  
    47  // UDPProxy is proxy for which handles UDP datagrams.
    48  // From libnetwork udp_proxy.go .
    49  type UDPProxy struct {
    50  	LogWriter      io.Writer
    51  	Listener       *net.UDPConn
    52  	BackendDial    func() (*net.UDPConn, error)
    53  	connTrackTable connTrackMap
    54  	connTrackLock  sync.Mutex
    55  }
    56  
    57  func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
    58  	defer func() {
    59  		proxy.connTrackLock.Lock()
    60  		delete(proxy.connTrackTable, *clientKey)
    61  		proxy.connTrackLock.Unlock()
    62  		proxyConn.Close()
    63  	}()
    64  
    65  	readBuf := make([]byte, UDPBufSize)
    66  	for {
    67  		proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
    68  	again:
    69  		read, err := proxyConn.Read(readBuf)
    70  		if err != nil {
    71  			if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
    72  				// This will happen if the last write failed
    73  				// (e.g: nothing is actually listening on the
    74  				// proxied port on the container), ignore it
    75  				// and continue until UDPConnTrackTimeout
    76  				// expires:
    77  				goto again
    78  			}
    79  			return
    80  		}
    81  		for i := 0; i != read; {
    82  			written, err := proxy.Listener.WriteToUDP(readBuf[i:read], clientAddr)
    83  			if err != nil {
    84  				return
    85  			}
    86  			i += written
    87  		}
    88  	}
    89  }
    90  
    91  // Run starts forwarding the traffic using UDP.
    92  func (proxy *UDPProxy) Run() {
    93  	proxy.connTrackTable = make(connTrackMap)
    94  	readBuf := make([]byte, UDPBufSize)
    95  	for {
    96  		read, from, err := proxy.Listener.ReadFromUDP(readBuf)
    97  		if err != nil {
    98  			// NOTE: Apparently ReadFrom doesn't return
    99  			// ECONNREFUSED like Read do (see comment in
   100  			// UDPProxy.replyLoop)
   101  			if !isClosedError(err) {
   102  				fmt.Fprintf(proxy.LogWriter, "Stopping proxy on udp: %v\n", err)
   103  			}
   104  			break
   105  		}
   106  
   107  		fromKey := newConnTrackKey(from)
   108  		proxy.connTrackLock.Lock()
   109  		proxyConn, hit := proxy.connTrackTable[*fromKey]
   110  		if !hit {
   111  			proxyConn, err = proxy.BackendDial()
   112  			if err != nil {
   113  				fmt.Fprintf(proxy.LogWriter, "Can't proxy a datagram to udp: %v\n", err)
   114  				proxy.connTrackLock.Unlock()
   115  				continue
   116  			}
   117  			proxy.connTrackTable[*fromKey] = proxyConn
   118  			go proxy.replyLoop(proxyConn, from, fromKey)
   119  		}
   120  		proxy.connTrackLock.Unlock()
   121  		for i := 0; i != read; {
   122  			written, err := proxyConn.Write(readBuf[i:read])
   123  			if err != nil {
   124  				fmt.Fprintf(proxy.LogWriter, "Can't proxy a datagram to udp: %v\n", err)
   125  				break
   126  			}
   127  			i += written
   128  		}
   129  	}
   130  }
   131  
   132  // Close stops forwarding the traffic.
   133  func (proxy *UDPProxy) Close() {
   134  	proxy.Listener.Close()
   135  	proxy.connTrackLock.Lock()
   136  	defer proxy.connTrackLock.Unlock()
   137  	for _, conn := range proxy.connTrackTable {
   138  		conn.Close()
   139  	}
   140  }
   141  
   142  func isClosedError(err error) bool {
   143  	/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
   144  	 * See:
   145  	 * http://golang.org/src/pkg/net/net.go
   146  	 * https://code.google.com/p/go/issues/detail?id=4337
   147  	 * https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
   148  	 */
   149  	return strings.HasSuffix(err.Error(), "use of closed network connection")
   150  }