github.com/elfadel/cilium@v1.6.12/pkg/fqdn/dnsproxy/udp.go (about) 1 // Copyright 2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dnsproxy 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/binary" 21 "fmt" 22 "net" 23 "strconv" 24 "sync" 25 "syscall" 26 "unsafe" 27 28 "golang.org/x/net/ipv4" 29 "golang.org/x/net/ipv6" 30 "golang.org/x/sys/unix" 31 32 "github.com/miekg/dns" 33 ) 34 35 // This is the required size of the OOB buffer to pass to ReadMsgUDP. 36 var udpOOBSize = func() int { 37 var hdr syscall.Cmsghdr 38 var addr unix.RawSockaddrInet6 39 return int(unsafe.Sizeof(hdr) + unsafe.Sizeof(addr)) 40 }() 41 42 type sessionUDPFactory struct { 43 // A pool for UDP message buffers. 44 udpPool sync.Pool 45 46 // ipv4Enabled and ipv6Enabled are used when setting up the proxy sockets 47 // later, and determine if we bind to 127.0.0.1 and ::1, respectively. 48 // See sessionUDPFactory.SetSocketOptions 49 ipv4Enabled, ipv6Enabled bool 50 } 51 52 // sessionUDP implements the dns.SessionUDP, holding the remote address and the associated 53 // out-of-band data. 54 type sessionUDP struct { 55 f *sessionUDPFactory // owner 56 conn *net.UDPConn // UDP socket for receiving both IPv4 and IPv6 57 raddr *net.UDPAddr 58 laddr *net.UDPAddr 59 m []byte 60 oob []byte 61 } 62 63 var rawconn4 *net.IPConn // raw socket for sending IPv4 64 var rawconn6 *net.IPConn // raw socket for sending IPv6 65 66 // Set the socket options needed for tranparent proxying for the listening socket 67 // IP(V6)_TRANSPARENT allows socket to receive packets with any destination address/port 68 // IP(V6)_RECVORIGDSTADDR tells the kernel to pass the original destination address/port on recvmsg 69 // The socket may be receiving both IPv4 and IPv6 data, so set both options, if enabled. 70 func transparentSetsockopt(fd int, ipv4, ipv6 bool) error { 71 var err4, err6 error 72 if ipv6 { 73 err6 = unix.SetsockoptInt(fd, unix.SOL_IPV6, unix.IPV6_TRANSPARENT, 1) 74 if err6 == nil { 75 err6 = unix.SetsockoptInt(fd, unix.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1) 76 } 77 if err6 != nil { 78 return err6 79 } 80 } 81 if ipv4 { 82 err4 = unix.SetsockoptInt(fd, unix.SOL_IP, unix.IP_TRANSPARENT, 1) 83 if err4 == nil { 84 err4 = unix.SetsockoptInt(fd, unix.SOL_IP, unix.IP_RECVORIGDSTADDR, 1) 85 } 86 if err4 != nil { 87 return err4 88 } 89 } 90 return nil 91 } 92 93 func listenConfig(mark int, ipv4, ipv6 bool) *net.ListenConfig { 94 return &net.ListenConfig{ 95 Control: func(network, address string, c syscall.RawConn) error { 96 var opErr error 97 err := c.Control(func(fd uintptr) { 98 opErr = transparentSetsockopt(int(fd), ipv4, ipv6) 99 if opErr == nil && mark != 0 { 100 opErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, mark) 101 } 102 if opErr == nil { 103 opErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) 104 } 105 }) 106 if err != nil { 107 return err 108 } 109 110 return opErr 111 }} 112 } 113 114 func bindUDP(addr string, ipv4, ipv6 bool) *net.IPConn { 115 // Mark outgoing packets as proxy egress return traffic (0x0b00) 116 conn, err := listenConfig(0xb00, ipv4, ipv6).ListenPacket(context.Background(), "ip:udp", addr) 117 if err != nil { 118 log.Errorf("bindUDP failed for address %s: %s", addr, err) 119 return nil 120 } 121 return conn.(*net.IPConn) 122 } 123 124 // NOTE: udpOnce is used in SetSocketOptions below, but assumes we have a 125 // global singleton sessionUDPFactory. This is created in StartDNSProxy in 126 // order to have option.Config.EnableIPv{4,6} parsed correctly. 127 var udpOnce sync.Once 128 129 // SetSocketOptions set's up 'conn' to be used with a SessionUDP. 130 func (f *sessionUDPFactory) SetSocketOptions(conn *net.UDPConn) error { 131 // Set up the raw socket for sending responses. 132 // - Must use a raw UDP socket for sending responses so that we can send 133 // from a specific port without binding to it. 134 // - The raw UDP socket must be bound to a specific IP address to prevent 135 // it receiving ALL UDP packets on the host. 136 // - We use oob data to override the source IP address when sending 137 // - Must use separate sockets for IPv4/IPv6, as sending to a v6-mapped 138 // v4 address from a socket bound to "::1" does not work due to kernel 139 // checking that a route exists from the source address before 140 // the source address is replaced with the (transparently) changed one 141 udpOnce.Do(func() { 142 if f.ipv4Enabled { 143 rawconn4 = bindUDP("127.0.0.1", f.ipv4Enabled, false) // raw socket for sending IPv4 144 } 145 if f.ipv6Enabled { 146 rawconn6 = bindUDP("::1", false, f.ipv6Enabled) // raw socket for sending IPv6 147 } 148 }) 149 if (f.ipv4Enabled && rawconn4 == nil) || (f.ipv6Enabled && rawconn6 == nil) { 150 return fmt.Errorf("Unable to open raw UDP sockets for DNS Proxy") 151 } 152 return nil 153 } 154 155 // InitPool initializes a pool of buffers to be used with SessionUDP. 156 func (f *sessionUDPFactory) InitPool(msgSize int) { 157 f.udpPool.New = func() interface{} { 158 return &sessionUDP{ 159 f: f, 160 m: make([]byte, msgSize), 161 oob: make([]byte, udpOOBSize), 162 } 163 } 164 } 165 166 // ReadRequest reads a single request from 'conn' and returns the request context 167 func (f *sessionUDPFactory) ReadRequest(conn *net.UDPConn) ([]byte, dns.SessionUDP, error) { 168 s := f.udpPool.Get().(*sessionUDP) 169 n, oobn, _, raddr, err := conn.ReadMsgUDP(s.m, s.oob) 170 if err != nil { 171 s.Discard() 172 return nil, nil, err 173 } 174 s.conn = conn 175 s.raddr = raddr 176 s.m = s.m[:n] // Re-slice to the actual size 177 s.oob = s.oob[:oobn] // Re-slice to the actual size 178 s.laddr, err = parseDstFromOOB(s.oob) 179 if err != nil { 180 s.Discard() 181 return nil, nil, err 182 } 183 return s.m, s, err 184 } 185 186 // Discard returns 's' to the factory pool 187 func (s *sessionUDP) Discard() { 188 s.conn = nil 189 s.raddr = nil 190 s.laddr = nil 191 s.m = s.m[:cap(s.m)] 192 s.oob = s.oob[:cap(s.oob)] 193 194 s.f.udpPool.Put(s) 195 } 196 197 // RemoteAddr returns the remote network address. 198 func (s *sessionUDP) RemoteAddr() net.Addr { return s.raddr } 199 200 // LocalAddr returns the local network address for the current request. 201 func (s *sessionUDP) LocalAddr() net.Addr { return s.laddr } 202 203 // WriteResponse writes a response to a request received earlier 204 func (s *sessionUDP) WriteResponse(b []byte) (int, error) { 205 // Must give the UDP header to get the source port right. 206 // Reuse the msg buffer, figure out if golang can do gatter-scather IO 207 // with raw sockets? 208 bb := bytes.NewBuffer(s.m[:0]) 209 binary.Write(bb, binary.BigEndian, uint16(s.laddr.Port)) 210 binary.Write(bb, binary.BigEndian, uint16(s.raddr.Port)) 211 binary.Write(bb, binary.BigEndian, uint16(8+len(b))) 212 binary.Write(bb, binary.BigEndian, uint16(0)) // checksum 213 bb.Write(b) 214 buf := bb.Bytes() 215 216 var n int 217 var err error 218 dst := net.IPAddr{ 219 IP: s.raddr.IP, 220 } 221 if s.raddr.IP.To4() == nil { 222 n, _, err = rawconn6.WriteMsgIP(buf, s.controlMessage(s.laddr), &dst) 223 } else { 224 n, _, err = rawconn4.WriteMsgIP(buf, s.controlMessage(s.laddr), &dst) 225 } 226 if err != nil { 227 log.Warningf("WriteMsgIP: %s", err) 228 } else { 229 log.Debugf("WriteMsgIP: wrote %d bytes", n) 230 } 231 return n, err 232 } 233 234 // parseDstFromOOB takes oob data and returns the destination IP. 235 func parseDstFromOOB(oob []byte) (*net.UDPAddr, error) { 236 msgs, err := syscall.ParseSocketControlMessage(oob) 237 if err != nil { 238 return nil, fmt.Errorf("parsing socket control message: %s", err) 239 } 240 241 for _, msg := range msgs { 242 if msg.Header.Level == unix.SOL_IP && msg.Header.Type == unix.IP_ORIGDSTADDR { 243 pp := &syscall.RawSockaddrInet4{} 244 // Address family is in native byte order 245 family := *(*uint16)(unsafe.Pointer(&msg.Data[unsafe.Offsetof(pp.Family)])) 246 if family != unix.AF_INET { 247 return nil, fmt.Errorf("original destination is not IPv4.") 248 } 249 // Port is in big-endian byte order 250 if err = binary.Read(bytes.NewReader(msg.Data), binary.BigEndian, pp); err != nil { 251 return nil, fmt.Errorf("reading original destination address: %s", err) 252 } 253 laddr := &net.UDPAddr{ 254 IP: net.IPv4(pp.Addr[0], pp.Addr[1], pp.Addr[2], pp.Addr[3]), 255 Port: int(pp.Port), 256 } 257 return laddr, nil 258 } 259 if msg.Header.Level == unix.SOL_IPV6 && msg.Header.Type == unix.IPV6_ORIGDSTADDR { 260 pp := &syscall.RawSockaddrInet6{} 261 // Address family is in native byte order 262 family := *(*uint16)(unsafe.Pointer(&msg.Data[unsafe.Offsetof(pp.Family)])) 263 if family != unix.AF_INET6 { 264 return nil, fmt.Errorf("original destination is not IPv6.") 265 } 266 // Scope ID is in native byte order 267 scopeId := *(*uint32)(unsafe.Pointer(&msg.Data[unsafe.Offsetof(pp.Scope_id)])) 268 // Rest of the data is big-endian (port) 269 if err = binary.Read(bytes.NewReader(msg.Data), binary.BigEndian, pp); err != nil { 270 return nil, fmt.Errorf("reading original destination address: %s", err) 271 } 272 laddr := &net.UDPAddr{ 273 IP: net.IP(pp.Addr[:]), 274 Port: int(pp.Port), 275 Zone: strconv.Itoa(int(scopeId)), 276 } 277 return laddr, nil 278 } 279 } 280 return nil, fmt.Errorf("No original destination found!") 281 } 282 283 // correctSource returns the oob data with the given source address 284 func (s *sessionUDP) controlMessage(src *net.UDPAddr) []byte { 285 // If the src is definitely an IPv6, then use ipv6's ControlMessage to 286 // respond otherwise use ipv4's because ipv6's marshal ignores ipv4 287 // addresses. 288 if src.IP.To4() == nil { 289 cm := new(ipv6.ControlMessage) 290 cm.Src = src.IP 291 return cm.Marshal() 292 } 293 cm := new(ipv4.ControlMessage) 294 cm.Src = src.IP 295 return cm.Marshal() 296 }