github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/libnetwork/cmd/proxy/udp_proxy.go (about)

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