// 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 }