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