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 }