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 }