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 }