github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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 }