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  }