github.com/mizzy/docker@v1.5.0/pkg/proxy/udp_proxy.go (about)

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