func (d *DHCPService) getRequestState(packet dhcp4.Packet, reqOptions dhcp4.Options) (string, net.IP) { state := "NEW" requestedIP := net.IP(reqOptions[dhcp4.OptionRequestedIPAddress]) if len(requestedIP) == 0 || requestedIP.IsUnspecified() { // empty state = "RENEWAL" requestedIP = packet.CIAddr() } return state, requestedIP }
func (h *DHCPHandler) ServeDHCP(p dhcp.Packet, msgType dhcp.MessageType, options dhcp.Options) (d dhcp.Packet) { switch msgType { case dhcp.Discover: free, nic := -1, p.CHAddr().String() for i, v := range h.leases { // Find previous lease if v.nic == nic { free = i goto reply } } if free = h.freeLease(); free == -1 { return } reply: return dhcp.ReplyPacket(p, dhcp.Offer, h.ip, dhcp.IPAdd(h.start, free), h.leaseDuration, h.options.SelectOrderOrAll(options[dhcp.OptionParameterRequestList])) case dhcp.Request: if server, ok := options[dhcp.OptionServerIdentifier]; ok && !net.IP(server).Equal(h.ip) { return nil // Message not for this dhcp server } reqIP := net.IP(options[dhcp.OptionRequestedIPAddress]) if reqIP == nil { reqIP = net.IP(p.CIAddr()) } if len(reqIP) == 4 && !reqIP.Equal(net.IPv4zero) { if leaseNum := dhcp.IPRange(h.start, reqIP) - 1; leaseNum >= 0 && leaseNum < h.leaseRange { if l, exists := h.leases[leaseNum]; !exists || l.nic == p.CHAddr().String() { h.leases[leaseNum] = lease{nic: p.CHAddr().String(), expiry: time.Now().Add(h.leaseDuration)} return dhcp.ReplyPacket(p, dhcp.ACK, h.ip, net.IP(options[dhcp.OptionRequestedIPAddress]), h.leaseDuration, h.options.SelectOrderOrAll(options[dhcp.OptionParameterRequestList])) } } } return dhcp.ReplyPacket(p, dhcp.NAK, h.ip, nil, 0, nil) case dhcp.Release, dhcp.Decline: nic := p.CHAddr().String() for i, v := range h.leases { if v.nic == nic { delete(h.leases, i) break } } } return nil }
// ReplyPacket creates a reply packet that a Server would send to a client. // It uses the req Packet param to copy across common/necessary fields to // associate the reply with the request. func informReplyPacket(req dhcp4.Packet, mt dhcp4.MessageType, serverID net.IP, options []dhcp4.Option) dhcp4.Packet { p := dhcp4.NewPacket(dhcp4.BootReply) p.SetXId(req.XId()) p.SetHType(req.HType()) p[2] = req.HLen() // dhcp4 library does not provide a setter p.SetFlags(req.Flags()) p.SetCIAddr(req.CIAddr()) p.SetCHAddr(req.CHAddr()) p.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(mt)}) p.AddOption(dhcp4.OptionServerIdentifier, []byte(serverID)) for _, o := range options { p.AddOption(o.Code, o.Value) } p.PadToMinSize() return p }
func (h *DHCPHandler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) (d dhcp4.Packet) { var macAddress string = strings.Join(strings.Split(p.CHAddr().String(), ":"), "") switch msgType { case dhcp4.Discover: ip, err := h.leasePool.Assign(p.CHAddr().String()) if err != nil { logging.Debug("DHCP", "err in lease pool - %s", err.Error()) return nil // pool is full } replyOptions := h.dhcpOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]) packet := dhcp4.ReplyPacket(p, dhcp4.Offer, h.settings.ServerIP, ip, h.settings.LeaseDuration, replyOptions) // this is a pxe request guidVal, is_pxe := options[97] if is_pxe { logging.Log("DHCP", "dhcp discover with PXE - CHADDR %s - IP %s - our ip %s", p.CHAddr().String(), ip.String(), h.settings.ServerIP.String()) guid := guidVal[1:] packet.AddOption(60, []byte("PXEClient")) packet.AddOption(97, guid) packet.AddOption(43, h.fillPXE()) } else { logging.Log("DHCP", "dhcp discover - CHADDR %s - IP %s", p.CHAddr().String(), ip.String()) } return packet case dhcp4.Request: if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(h.settings.ServerIP) { return nil // this message is not ours } requestedIP := net.IP(options[dhcp4.OptionRequestedIPAddress]) if requestedIP == nil { requestedIP = net.IP(p.CIAddr()) } if len(requestedIP) != 4 || requestedIP.Equal(net.IPv4zero) { logging.Debug("DHCP", "dhcp request - CHADDR %s - bad request", p.CHAddr().String()) return nil } _, err := h.leasePool.Request(p.CHAddr().String(), requestedIP) if err != nil { logging.Debug("DHCP", "dhcp request - CHADDR %s - Requested IP %s - NO MATCH", p.CHAddr().String(), requestedIP.String()) return dhcp4.ReplyPacket(p, dhcp4.NAK, h.settings.ServerIP, nil, 0, nil) } replyOptions := h.dhcpOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]) packet := dhcp4.ReplyPacket(p, dhcp4.ACK, h.settings.ServerIP, requestedIP, h.settings.LeaseDuration, replyOptions) // this is a pxe request guidVal, is_pxe := options[97] if is_pxe { logging.Log("DHCP", "dhcp request with PXE - CHADDR %s - Requested IP %s - our ip %s - ACCEPTED", p.CHAddr().String(), requestedIP.String(), h.settings.ServerIP.String()) guid := guidVal[1:] packet.AddOption(60, []byte("PXEClient")) packet.AddOption(97, guid) packet.AddOption(43, h.fillPXE()) } else { logging.Log("DHCP", "dhcp request - CHADDR %s - Requested IP %s - ACCEPTED", p.CHAddr().String(), requestedIP.String()) } packet.AddOption(12, []byte("node"+macAddress)) // host name option return packet case dhcp4.Release, dhcp4.Decline: return nil } return nil }
// ServeDHCP is called by dhcp4.ListenAndServe when the service is started func (d *DHCPService) ServeDHCP(packet dhcp4.Packet, msgType dhcp4.MessageType, reqOptions dhcp4.Options) (response dhcp4.Packet) { switch msgType { case dhcp4.Discover: // RFC 2131 4.3.1 // FIXME: send to StatHat and/or increment a counter mac := packet.CHAddr() // Check MAC blacklist if !d.isMACPermitted(mac) { log.Printf("DHCP Discover from %s\n is not permitted", mac.String()) return nil } log.Printf("DHCP Discover from %s\n", mac.String()) // Look up the MAC entry with cascaded attributes lease, found, err := d.db.GetMAC(mac, true) if err != nil { return nil } // Existing Lease if found { options := d.getOptionsFromMAC(lease) log.Printf("DHCP Discover from %s (we offer %s from current lease)\n", lease.MAC.String(), lease.IP.String()) // for x, y := range reqOptions { // log.Printf("\tR[%v] %v %s\n", x, y, y) // } // for x, y := range options { // log.Printf("\tO[%v] %v %s\n", x, y, y) // } return dhcp4.ReplyPacket(packet, dhcp4.Offer, d.ip.To4(), lease.IP.To4(), d.getLeaseDurationForRequest(reqOptions, lease.Duration), options.SelectOrderOrAll(reqOptions[dhcp4.OptionParameterRequestList])) } // New Lease ip := d.getIPFromPool() if ip != nil { options := d.getOptionsFromMAC(lease) log.Printf("DHCP Discover from %s (we offer %s from pool)\n", mac.String(), ip.String()) // for x, y := range reqOptions { // log.Printf("\tR[%v] %v %s\n", x, y, y) // } // for x, y := range options { // log.Printf("\tO[%v] %v %s\n", x, y, y) // } return dhcp4.ReplyPacket(packet, dhcp4.Offer, d.ip.To4(), ip.To4(), d.getLeaseDurationForRequest(reqOptions, d.leaseDuration), options.SelectOrderOrAll(reqOptions[dhcp4.OptionParameterRequestList])) } log.Printf("DHCP Discover from %s (no offer due to no addresses available in pool)\n", mac.String()) // FIXME: Send to StatHat and/or increment a counter // TODO: Send an email? return nil case dhcp4.Request: // RFC 2131 4.3.2 // FIXME: send to StatHat and/or increment a counter mac := packet.CHAddr() // Check MAC blacklist if !d.isMACPermitted(mac) { log.Printf("DHCP Request from %s\n is not permitted", mac.String()) return nil } // Check IP presence state, requestedIP := d.getRequestState(packet, reqOptions) log.Printf("DHCP Request (%s) from %s...\n", state, mac.String()) if len(requestedIP) == 0 || requestedIP.IsUnspecified() { // no IP provided at all... why? FIXME log.Printf("DHCP Request (%s) from %s (empty IP, so we're just ignoring this request)\n", state, mac.String()) return nil } // Check IPv4 if len(requestedIP) != net.IPv4len { log.Printf("DHCP Request (%s) from %s wanting %s (IPv6 address requested, so we're just ignoring this request)\n", state, mac.String(), requestedIP.String()) return nil } // Check IP subnet if !d.subnet.Contains(requestedIP) { log.Printf("DHCP Request (%s) from %s wanting %s (we reject due to wrong subnet)\n", state, mac.String(), requestedIP.String()) return dhcp4.ReplyPacket(packet, dhcp4.NAK, d.ip.To4(), nil, 0, nil) } // Check Target Server targetServerIP := packet.SIAddr() if len(targetServerIP) > 0 && !targetServerIP.IsUnspecified() { log.Printf("DHCP Request (%s) from %s wanting %s is in response to a DHCP offer from %s\n", state, mac.String(), requestedIP.String(), targetServerIP.String()) if d.ip.Equal(targetServerIP) { return nil } } // Process Request log.Printf("DHCP Request (%s) from %s wanting %s...\n", state, mac.String(), requestedIP.String()) lease, found, err := d.db.GetMAC(mac, true) if err != nil { return nil } if found { // Existing Lease lease.Duration = d.getLeaseDurationForRequest(reqOptions, d.leaseDuration) if lease.IP.Equal(requestedIP) { err = d.db.RenewLease(lease) } else { log.Printf("DHCP Request (%s) from %s wanting %s (we reject due to lease mismatch, should be %s)\n", state, lease.MAC.String(), requestedIP.String(), lease.IP.String()) return dhcp4.ReplyPacket(packet, dhcp4.NAK, d.ip.To4(), nil, 0, nil) } } else { // Check IP subnet is within the guestPool (we don't want users requesting non-pool addresses unless we assigned it to their MAC, administratively) if !d.guestPool.Contains(requestedIP) { log.Printf("DHCP Request (%s) from %s wanting %s (we reject due to not being within the guestPool)\n", state, mac.String(), requestedIP.String()) return dhcp4.ReplyPacket(packet, dhcp4.NAK, d.ip.To4(), nil, 0, nil) } // New lease lease = &MACEntry{ MAC: mac, IP: requestedIP, Duration: d.getLeaseDurationForRequest(reqOptions, d.leaseDuration), } err = d.db.CreateLease(lease) } if err == nil { d.maintainDNSRecords(lease, packet, reqOptions) // TODO: Move this? options := d.getOptionsFromMAC(lease) log.Printf("DHCP Request (%s) from %s wanting %s (we agree)\n", state, mac.String(), requestedIP.String()) return dhcp4.ReplyPacket(packet, dhcp4.ACK, d.ip.To4(), requestedIP.To4(), lease.Duration, options.SelectOrderOrAll(reqOptions[dhcp4.OptionParameterRequestList])) } log.Printf("DHCP Request (%s) from %s wanting %s (we reject due to address collision)\n", state, mac.String(), requestedIP.String()) return dhcp4.ReplyPacket(packet, dhcp4.NAK, d.ip.To4(), nil, 0, nil) case dhcp4.Decline: // RFC 2131 4.3.3 // FIXME: release from DB? tick a flag? increment a counter? send to StatHat? mac := packet.CHAddr() log.Printf("DHCP Decline from %s\n", mac.String()) case dhcp4.Release: // RFC 2131 4.3.4 // FIXME: release from DB? tick a flag? increment a counter? send to StatHat? mac := packet.CHAddr() log.Printf("DHCP Release from %s\n", mac.String()) case dhcp4.Inform: // RFC 2131 4.3.5 // https://tools.ietf.org/html/draft-ietf-dhc-dhcpinform-clarify-06 // FIXME: release from DB? tick a flag? increment a counter? send to StatHat? // FIXME: we should reply with valuable info, but not assign an IP to this client, per RFC 2131 for DHCPINFORM // NOTE: the client's IP is supposed to only be in the ciaddr field, not the requested IP field, per RFC 2131 4.4.3 mac := packet.CHAddr() ip := packet.CIAddr() if len(ip) > 0 && !ip.IsUnspecified() { log.Printf("DHCP Inform from %s for %s \n", mac.String(), ip.String()) if len(ip) == net.IPv4len && d.guestPool.Contains(ip) { entry, found, _ := d.db.GetMAC(mac, true) if found { options := d.getOptionsFromMAC(entry) return informReplyPacket(packet, dhcp4.ACK, d.ip.To4(), options.SelectOrderOrAll(reqOptions[dhcp4.OptionParameterRequestList])) } } } } return nil }
// ServeDHCP replies a dhcp request func (h *Handler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) (d dhcp4.Packet) { switch msgType { case dhcp4.Discover, dhcp4.Request: if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(h.serverIP) { if msgType == dhcp4.Discover { log.WithField("where", "dhcp.ServeDHCP").Debugf( "identifying dhcp server in Discover?! (%v)", p) } return nil // this message is not ours } machineInterface := h.datasource.MachineInterface(p.CHAddr()) machine, err := machineInterface.Machine(true, nil) if err != nil { log.WithField("where", "dhcp.ServeDHCP").WithError(err).Warn( "failed to get machine") return nil } netConfStr, err := machineInterface.GetVariable(datasource.SpecialKeyNetworkConfiguration) if err != nil { log.WithField("where", "dhcp.ServeDHCP").WithError(err).Warn( "failed to get network configuration") return nil } netConf, err := datasource.UnmarshalNetworkConfiguration(netConfStr) if err != nil { log.WithField("where", "dhcp.ServeDHCP").WithError(err).Warn( "failed to unmarshal network-configuration=%q", netConfStr) return nil } instanceInfos, err := h.datasource.Instances() if err != nil { log.WithField("where", "dhcp.ServeDHCP").WithError(err).Warn( "failed to get instances") return nil } hostname := strings.Join(strings.Split(p.CHAddr().String(), ":"), "") hostname += "." + h.datasource.ClusterName() dhcpOptions := dhcp4.Options{ dhcp4.OptionSubnetMask: netConf.Netmask.To4(), dhcp4.OptionDomainNameServer: dnsAddressesForDHCP(&instanceInfos), dhcp4.OptionHostName: []byte(hostname), } if netConf.Router != nil { dhcpOptions[dhcp4.OptionRouter] = netConf.Router.To4() } if len(netConf.ClasslessRouteOption) != 0 { var res []byte for _, part := range netConf.ClasslessRouteOption { res = append(res, part.ToBytes()...) } dhcpOptions[dhcp4.OptionClasslessRouteFormat] = res } responseMsgType := dhcp4.Offer if msgType == dhcp4.Request { responseMsgType = dhcp4.ACK requestedIP := net.IP(options[dhcp4.OptionRequestedIPAddress]) if requestedIP == nil { requestedIP = net.IP(p.CIAddr()) } if len(requestedIP) != 4 || requestedIP.Equal(net.IPv4zero) { log.WithFields(log.Fields{ "where": "dhcp.ServeDHCP", "object": p.CHAddr().String(), "subject": msgType, }).Debugf("bad request") return nil } if !requestedIP.Equal(machine.IP) { log.WithFields(log.Fields{ "where": "dhcp.ServeDHCP", "object": p.CHAddr().String(), "subject": msgType, }).Debugf("requestedIP(%s) != assignedIp(%s)", requestedIP.String(), machine.IP.String()) return nil } machineInterface.CheckIn() } guidVal, isPxe := options[97] log.WithFields(log.Fields{ "where": "dhcp.ServeDHCP", "action": "debug", "object": p.CHAddr().String(), "subject": msgType, }).Infof("assignedIp=%s isPxe=%v", machine.IP.String(), isPxe) replyOptions := dhcpOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]) if isPxe { // this is a pxe request guid := guidVal[1:] replyOptions = append(replyOptions, dhcp4.Option{ Code: dhcp4.OptionVendorClassIdentifier, Value: []byte("PXEClient"), }, dhcp4.Option{ Code: 97, // UUID/GUID-based Client Identifier Value: guid, }, dhcp4.Option{ Code: dhcp4.OptionVendorSpecificInformation, Value: h.fillPXE(), }, ) hash, err := h.datasource.GetClusterVariable(datasource.ActiveWorkspaceHashKey) if err == nil { machineInterface.SetVariable("booted-workspace-hash", hash) } } packet := dhcp4.ReplyPacket(p, responseMsgType, h.serverIP, machine.IP, randLeaseDuration(), replyOptions) return packet case dhcp4.Release, dhcp4.Decline: return nil } return nil }
func (h *DHCPHandler) ServeDHCP(p dhcp.Packet, msgType dhcp.MessageType, options dhcp.Options) (d dhcp.Packet) { // First find the subnet to use. giaddr field to lookup subnet if not all zeros. // If all zeros, use the interfaces Addrs to find a subnet, first wins. var subnet *Subnet subnet = nil giaddr := p.GIAddr() if !giaddr.Equal(net.IPv4zero) { subnet = h.info.FindSubnet(giaddr) } else { log.Println("Received Broadcast/Local message on ", h.intf.Name) addrs, err := h.intf.Addrs() if err != nil { log.Println("Can't find addresses for ", h.intf.Name, ": ", err) } for _, a := range addrs { aip, _, _ := net.ParseCIDR(a.String()) // Only operate on v4 addresses if aip.To4() == nil { continue } subnet = h.info.FindSubnet(aip) if subnet != nil { break } } if ignore_anonymus { // Search all subnets for a binding. First wins log.Println("Looking up bound subnet for ", p.CHAddr().String()) subnet = h.info.FindBoundIP(p.CHAddr()) } if subnet == nil { // We didn't find a subnet for the interface. Look for the assigned server IP subnet = h.info.FindSubnet(h.ip) } } if subnet == nil { log.Println("Can not find subnet for packet, ignoring") return } nic := p.CHAddr().String() switch msgType { case dhcp.Discover: lease, binding := subnet.find_or_get_info(h.info, nic, p.CIAddr()) if lease == nil { log.Println("Out of IPs for ", subnet.Name, ", ignoring") return nil } // Ignore unknown MAC address if ignore_anonymus && binding == nil { log.Println("Ignoring request from unknown MAC address") return dhcp.ReplyPacket(p, dhcp.NAK, h.ip, nil, 0, nil) } options, lease_time := subnet.build_options(lease, binding) reply := dhcp.ReplyPacket(p, dhcp.Offer, h.ip, lease.Ip, lease_time, subnet.Options.SelectOrderOrAll(options[dhcp.OptionParameterRequestList])) log.Println("Discover: Handing out: ", reply.YIAddr(), " to ", reply.CHAddr()) return reply case dhcp.Request: server, ok := options[dhcp.OptionServerIdentifier] if ok && !net.IP(server).Equal(h.ip) { return nil // Message not for this dhcp server } reqIP := net.IP(options[dhcp.OptionRequestedIPAddress]) if reqIP == nil { reqIP = net.IP(p.CIAddr()) } if len(reqIP) != 4 || reqIP.Equal(net.IPv4zero) { return dhcp.ReplyPacket(p, dhcp.NAK, h.ip, nil, 0, nil) } lease, binding := subnet.find_info(h.info, nic) // Ignore unknown MAC address if ignore_anonymus && binding == nil { log.Println("Ignoring request from unknown MAC address") return dhcp.ReplyPacket(p, dhcp.NAK, h.ip, nil, 0, nil) } if lease == nil || !lease.Ip.Equal(reqIP) { return dhcp.ReplyPacket(p, dhcp.NAK, h.ip, nil, 0, nil) } options, lease_time := subnet.build_options(lease, binding) subnet.update_lease_time(h.info, lease, lease_time) reply := dhcp.ReplyPacket(p, dhcp.ACK, h.ip, lease.Ip, lease_time, subnet.Options.SelectOrderOrAll(options[dhcp.OptionParameterRequestList])) if binding != nil && binding.NextServer != nil { reply.SetSIAddr(net.ParseIP(*binding.NextServer)) } else if subnet.NextServer != nil { reply.SetSIAddr(*subnet.NextServer) } log.Println("Request: Handing out: ", reply.YIAddr(), " to ", reply.CHAddr()) return reply case dhcp.Release, dhcp.Decline: nic := p.CHAddr().String() subnet.free_lease(h.info, nic) } return nil }