github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/dhcp/server.go (about)

     1  /*
     2  Copyright 2016-2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  
    16  Parts of this file are copied from https://github.com/google/netboot/blob/8e5c0d07937f8c1dea6e5f218b64f6b95c32ada3/pixiecore/dhcp.go
    17  
    18  */
    19  
    20  package dhcp
    21  
    22  import (
    23  	"bytes"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"net"
    28  	"strings"
    29  
    30  	cnicurrent "github.com/containernetworking/cni/pkg/types/current"
    31  	"github.com/golang/glog"
    32  	"go.universe.tf/netboot/dhcp4"
    33  
    34  	"github.com/Mirantis/virtlet/pkg/network"
    35  )
    36  
    37  const (
    38  	serverPort = 67
    39  	// option 121 is for static routes as defined in rfc3442
    40  	classlessRouteOption = 121
    41  )
    42  
    43  var (
    44  	defaultDNS = []byte{8, 8, 8, 8}
    45  )
    46  
    47  // Server implements a DHCP server that runs in the container network namespace.
    48  type Server struct {
    49  	config   *network.ContainerSideNetwork
    50  	listener *dhcp4.Conn
    51  }
    52  
    53  // NewServer returns an initialized instance of Server.
    54  func NewServer(config *network.ContainerSideNetwork) *Server {
    55  	return &Server{config: config}
    56  }
    57  
    58  // SetupListener sets up a DHCP4 listener that listens on the default DHCP
    59  // port and the specified IP address.
    60  func (s *Server) SetupListener(laddr string) error {
    61  	var listener *dhcp4.Conn
    62  	var err error
    63  	if listener, err = dhcp4.NewConn(fmt.Sprintf("%s:%d", laddr, serverPort)); err != nil {
    64  		return err
    65  	}
    66  	s.listener = listener
    67  
    68  	return nil
    69  }
    70  
    71  // Close shuts down DHCP listener.
    72  func (s *Server) Close() error {
    73  	return s.listener.Close()
    74  }
    75  
    76  // Serve waits in an endless loop for DHCP requests and answers them.
    77  func (s *Server) Serve() error {
    78  	for {
    79  		pkt, intf, err := s.listener.RecvDHCP()
    80  		if err != nil {
    81  			return fmt.Errorf("receiving DHCP packet: %v", err)
    82  		}
    83  		if intf == nil {
    84  			return fmt.Errorf("received DHCP packet with no interface information - please fill a bug to https://github.com/google/netboot")
    85  		}
    86  		glog.V(2).Infof("Received dhcp packet from: %s", pkt.HardwareAddr.String())
    87  
    88  		serverIP, err := interfaceIP(intf)
    89  		if err != nil {
    90  			glog.Warningf("Want to respond to %s on %s, but couldn't get a source address: %s", pkt.HardwareAddr.String(), intf.Name, err)
    91  			continue
    92  		}
    93  
    94  		var resp *dhcp4.Packet
    95  		switch pkt.Type {
    96  		case dhcp4.MsgDiscover:
    97  			resp, err = s.offerDHCP(pkt, serverIP)
    98  			if err != nil {
    99  				glog.Warningf("Failed to construct DHCP offer for %s: %s", pkt.HardwareAddr.String(), err)
   100  				continue
   101  			}
   102  		case dhcp4.MsgRequest:
   103  			resp, err = s.ackDHCP(pkt, serverIP)
   104  			if err != nil {
   105  				glog.Warningf("Failed to construct DHCP ACK for %s: %s", pkt.HardwareAddr.String(), err)
   106  				continue
   107  			}
   108  		default:
   109  			glog.Warningf("Ignoring packet from %s: packet is %s", pkt.HardwareAddr.String(), pkt.Type.String())
   110  			continue
   111  		}
   112  
   113  		if resp != nil {
   114  			glog.V(2).Infof("Sending %s packet to %s", resp.Type.String(), pkt.HardwareAddr.String())
   115  			glog.V(3).Info(resp.DebugString())
   116  			if err = s.listener.SendDHCP(resp, intf); err != nil {
   117  				glog.Warningf("Failed to send DHCP offer for %s: %s", pkt.HardwareAddr.String(), err)
   118  			}
   119  		}
   120  	}
   121  }
   122  
   123  func interfaceIP(intf *net.Interface) (net.IP, error) {
   124  	addrs, err := intf.Addrs()
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	// Try to find an IPv4 address to use, in the following order:
   130  	// global unicast (includes rfc1918), link-local unicast,
   131  	// loopback.
   132  	fs := [](func(net.IP) bool){
   133  		net.IP.IsGlobalUnicast,
   134  		net.IP.IsLinkLocalUnicast,
   135  		net.IP.IsLoopback,
   136  	}
   137  	for _, f := range fs {
   138  		for _, a := range addrs {
   139  			ipaddr, ok := a.(*net.IPNet)
   140  			if !ok {
   141  				continue
   142  			}
   143  			ip := ipaddr.IP.To4()
   144  			if ip == nil {
   145  				continue
   146  			}
   147  			if f(ip) {
   148  				return ip, nil
   149  			}
   150  		}
   151  	}
   152  
   153  	return nil, errors.New("no usable unicast address configured on interface")
   154  }
   155  
   156  func (s *Server) getInterfaceNo(hwAddr net.HardwareAddr) int {
   157  	addr := hwAddr.String()
   158  	for i, permitted := range s.config.Result.Interfaces {
   159  		if permitted.Mac == addr {
   160  			return i
   161  		}
   162  	}
   163  	return -1
   164  }
   165  
   166  func (s *Server) prepareResponse(pkt *dhcp4.Packet, serverIP net.IP, mt dhcp4.MessageType) (*dhcp4.Packet, error) {
   167  	interfaceNo := s.getInterfaceNo(pkt.HardwareAddr)
   168  	if interfaceNo < 0 {
   169  		return nil, fmt.Errorf("unexpected packet from %v", pkt.HardwareAddr)
   170  	}
   171  
   172  	var cfg *cnicurrent.IPConfig
   173  	for _, curCfg := range s.config.Result.IPs {
   174  		if curCfg.Version == "4" && curCfg.Interface == interfaceNo {
   175  			cfg = curCfg
   176  		}
   177  	}
   178  	var mtu uint16
   179  	for _, iface := range s.config.Interfaces {
   180  		if bytes.Compare(pkt.HardwareAddr, iface.HardwareAddr) == 0 {
   181  			mtu = iface.MTU
   182  		}
   183  	}
   184  	if mtu == 0 {
   185  		return nil, fmt.Errorf("packet from mac address %s not found in CSN", pkt.HardwareAddr.String())
   186  	}
   187  
   188  	if cfg == nil {
   189  		return nil, fmt.Errorf("IPv4 config for interface %s is not specified in CNI config", pkt.HardwareAddr.String())
   190  	}
   191  
   192  	p := &dhcp4.Packet{
   193  		Type:          mt,
   194  		TransactionID: pkt.TransactionID,
   195  		Broadcast:     true,
   196  		HardwareAddr:  pkt.HardwareAddr,
   197  		RelayAddr:     pkt.RelayAddr,
   198  		ServerAddr:    serverIP,
   199  		Options:       make(dhcp4.Options),
   200  	}
   201  	p.Options[dhcp4.OptServerIdentifier] = serverIP
   202  
   203  	// if guid was sent, copy it
   204  	if pkt.Options[97] != nil {
   205  		p.Options[97] = pkt.Options[97]
   206  	}
   207  
   208  	p.YourAddr = cfg.Address.IP
   209  	p.Options[dhcp4.OptSubnetMask] = cfg.Address.Mask
   210  
   211  	// MTU option
   212  	p.Options[26] = []byte{uint8(mtu >> 8), uint8(mtu & 0xff)}
   213  
   214  	router, routeData, err := s.getStaticRoutes(cfg.Address)
   215  	if err != nil {
   216  		glog.Warningf("Can not transform static routes for mac %v: %v", pkt.HardwareAddr, err)
   217  	}
   218  	if router != nil {
   219  		p.Options[dhcp4.OptRouters] = router
   220  	}
   221  	if routeData != nil {
   222  		p.Options[classlessRouteOption] = routeData
   223  	}
   224  
   225  	// 86400 - full 24h
   226  	p.Options[dhcp4.OptLeaseTime] = []byte{0, 1, 81, 128}
   227  
   228  	// 43200 - 12h
   229  	p.Options[dhcp4.OptRenewalTime] = []byte{0, 0, 168, 192}
   230  
   231  	// 64800 - 18h
   232  	p.Options[dhcp4.OptRebindingTime] = []byte{0, 0, 253, 32}
   233  
   234  	// TODO: include more dns options
   235  	if len(s.config.Result.DNS.Nameservers) == 0 {
   236  		p.Options[dhcp4.OptDNSServers] = defaultDNS
   237  	} else {
   238  		var b bytes.Buffer
   239  		for _, nsIP := range s.config.Result.DNS.Nameservers {
   240  			ip := net.ParseIP(nsIP).To4()
   241  			if ip == nil {
   242  				glog.Warningf("failed to parse nameserver ip %q", nsIP)
   243  			} else {
   244  				b.Write(ip)
   245  			}
   246  		}
   247  		if b.Len() > 0 {
   248  			p.Options[dhcp4.OptDNSServers] = b.Bytes()
   249  		} else {
   250  			p.Options[dhcp4.OptDNSServers] = defaultDNS
   251  		}
   252  	}
   253  	if len(s.config.Result.DNS.Search) != 0 {
   254  		// https://tools.ietf.org/search/rfc3397
   255  		p.Options[119], err = compressedDomainList(s.config.Result.DNS.Search)
   256  		if err != nil {
   257  			return nil, err
   258  		}
   259  	}
   260  
   261  	return p, nil
   262  }
   263  
   264  func (s *Server) offerDHCP(pkt *dhcp4.Packet, serverIP net.IP) (*dhcp4.Packet, error) {
   265  	return s.prepareResponse(pkt, serverIP, dhcp4.MsgOffer)
   266  }
   267  
   268  func (s *Server) ackDHCP(pkt *dhcp4.Packet, serverIP net.IP) (*dhcp4.Packet, error) {
   269  	return s.prepareResponse(pkt, serverIP, dhcp4.MsgAck)
   270  }
   271  
   272  func (s *Server) getStaticRoutes(ip net.IPNet) (router, routes []byte, err error) {
   273  	if len(s.config.Result.Routes) == 0 {
   274  		return nil, nil, nil
   275  	}
   276  
   277  	var b bytes.Buffer
   278  	for _, route := range s.config.Result.Routes {
   279  		if route.Dst.IP == nil {
   280  			return nil, nil, fmt.Errorf("invalid route: %#v", route)
   281  		}
   282  		dstIP := route.Dst.IP.To4()
   283  		gw := route.GW
   284  		if gw == nil {
   285  			// FIXME: this should not be really needed for newer CNI
   286  			var cfg *cnicurrent.IPConfig
   287  			for _, curCfg := range s.config.Result.IPs {
   288  				if curCfg.Version == "4" {
   289  					cfg = curCfg
   290  				}
   291  			}
   292  			gw = cfg.Gateway.To4()
   293  			if gw == nil && cfg.Gateway != nil {
   294  				return nil, nil, fmt.Errorf("unexpected IPv6 gateway address: %#v", gw)
   295  			}
   296  		} else {
   297  			gw = gw.To4()
   298  		}
   299  		if !ip.Contains(gw) {
   300  			continue
   301  		}
   302  		if gw != nil && dstIP.Equal(net.IPv4zero) {
   303  			if s, _ := route.Dst.Mask.Size(); s == 0 {
   304  				router = gw
   305  				continue
   306  			}
   307  		}
   308  		writeRoute(&b, route.Dst, gw)
   309  	}
   310  
   311  	// XXX: classless routes apparently cause a problem:
   312  	// https://askubuntu.com/questions/767002/dhcp-connection-does-not-set-default-gateway-automatically
   313  	if b.Len() != 0 && router != nil {
   314  		writeRoute(&b, net.IPNet{
   315  			IP:   net.IPv4zero,
   316  			Mask: net.IPv4Mask(0, 0, 0, 0),
   317  		}, router)
   318  		router = nil
   319  	}
   320  	routes = b.Bytes()
   321  	return
   322  }
   323  
   324  // toDestinationDescriptor returns calculated destination descriptor according to rfc3442 (page 3)
   325  // warning: there is no check if ipnet is in required ipv4 type
   326  func toDestinationDescriptor(network net.IPNet) []byte {
   327  	s, _ := network.Mask.Size()
   328  	ipAsBytes := []byte(network.IP.To4())
   329  	return append(
   330  		[]byte{byte(s)},
   331  		ipAsBytes[:widthOfMaskToSignificantOctets(s)]...,
   332  	)
   333  }
   334  
   335  func writeRoute(w io.Writer, dst net.IPNet, gw net.IP) {
   336  	w.Write(toDestinationDescriptor(dst))
   337  	if gw != nil {
   338  		w.Write(gw)
   339  	} else {
   340  		w.Write([]byte{0, 0, 0, 0})
   341  	}
   342  }
   343  
   344  func widthOfMaskToSignificantOctets(mask int) int {
   345  	return (mask + 7) / 8
   346  }
   347  
   348  func compressedDomainList(domainList []string) ([]byte, error) {
   349  	// https://tools.ietf.org/search/rfc1035#section-4.1.4
   350  	// simplified version, only encoding, without real compression
   351  	var b bytes.Buffer
   352  	for n, domain := range domainList {
   353  		// add '\0' between entries
   354  		if n > 0 {
   355  			b.WriteByte(0)
   356  		}
   357  
   358  		// encode domain parts as (single byte length)(string)
   359  		parts := strings.Split(domain, ".")
   360  		for _, part := range parts {
   361  			if len(part) > 254 {
   362  				return nil, fmt.Errorf("domain name element '%s' exceeds 254 length limit", part)
   363  			}
   364  			b.WriteByte(byte(len(part)))
   365  			b.WriteString(part)
   366  		}
   367  	}
   368  
   369  	return b.Bytes(), nil
   370  }