package reservation import ( "context" "dhcp/data" "errors" "github.com/go-logr/logr" "github.com/insomniacslk/dhcp/dhcpv4" "github.com/rs/zerolog/log" "golang.org/x/net/ipv4" "net" ) // setDefaults will update the Handler struct to have default values so as // to avoid panic for nil pointers and such. func (h *Handler) setDefaults() { if h.Backend == nil { h.Backend = noop{} } if h.Log.GetSink() == nil { h.Log = logr.Discard() } } // Handle responds to dhcpServer messages with dhcpServer server options. func (h *Handler) Handle(ctx context.Context, conn *ipv4.PacketConn, p data.Packet) { h.setDefaults() if p.Pkt == nil { h.Log.Error(errors.New("incoming packet is nil"), "not able to respond when the incoming packet is nil") return } upeer, ok := p.Peer.(*net.UDPAddr) if !ok { h.Log.Error(errors.New("peer is not a UDP connection"), "not able to respond when the peer is not a UDP connection") return } if upeer == nil { h.Log.Error(errors.New("peer is nil"), "not able to respond when the peer is nil") return } if conn == nil { h.Log.Error(errors.New("connection is nil"), "not able to respond when the connection is nil") return } var ifName string if p.Md != nil { ifName = p.Md.IfName } log := h.Log.WithValues("mac", p.Pkt.ClientHWAddr.String(), "xid", p.Pkt.TransactionID.String(), "interface", ifName) var reply *dhcpv4.DHCPv4 switch mt := p.Pkt.MessageType(); mt { case dhcpv4.MessageTypeDiscover: d, err := h.readBackend(ctx, p.Pkt.ClientHWAddr, ifName) if err != nil { if hardwareNotFound(err) { return } log.Info("error reading from macAndIPXE", "error", err) return } log.Info("received dhcpServer packet", "type", p.Pkt.MessageType().String()) reply = h.updateMsg(ctx, &p, d, dhcpv4.MessageTypeOffer) log = log.WithValues("type", dhcpv4.MessageTypeOffer.String()) case dhcpv4.MessageTypeRequest: d, err := h.readBackend(ctx, p.Pkt.ClientHWAddr, ifName) if err != nil { if hardwareNotFound(err) { return } log.Info("error reading from macAndIPXE", "error", err) return } log.Info("received dhcpServer packet", "type", p.Pkt.MessageType().String()) reply = h.updateMsg(ctx, &p, d, dhcpv4.MessageTypeAck) log = log.WithValues("type", dhcpv4.MessageTypeAck.String()) case dhcpv4.MessageTypeRelease: // 固定ip // 不需要客户端去主动释放 log.Info("received dhcpServer release packet, no response required, all IPs are host reservations", "type", p.Pkt.MessageType().String()) return default: log.Info("received unknown message type", "type", p.Pkt.MessageType().String()) return } if ns := reply.ServerIPAddr; ns != nil { log = log.WithValues("nextServer", ns.String()) } dst := replyDestination(p.Peer, p.Pkt.GatewayIPAddr) log = log.WithValues("ipAddress", reply.YourIPAddr.String(), "destination", dst.String()) cm := &ipv4.ControlMessage{} if p.Md != nil { cm.IfIndex = p.Md.IfIndex } if _, err := conn.WriteTo(reply.ToBytes(), cm, dst); err != nil { log.Error(err, "failed to send dhcpServer") return } log.Info("sent dhcpServer response") } // replyDestination determines the destination address for the dhcpServer reply. // If the giaddr is set, then the reply should be sent to the giaddr. // Otherwise, the reply should be sent to the direct peer. // // From page 22 of https://www.ietf.org/rfc/rfc2131.txt: // "If the 'giaddr' field in a dhcpServer message from a client is non-zero, // the server sends any return messages to the 'dhcpServer server' port on // the BOOTP relay agent whose address appears in 'giaddr'.". func replyDestination(directPeer net.Addr, giaddr net.IP) net.Addr { if !giaddr.IsUnspecified() && giaddr != nil { return &net.UDPAddr{IP: giaddr, Port: dhcpv4.ServerPort} } return directPeer } // readBackend encapsulates the macAndIPXE read and opentelemetry handling. func (h *Handler) readBackend(ctx context.Context, mac net.HardwareAddr, ifName string) (*data.DHCP, error) { h.setDefaults() d, err := h.Backend.GetByMac(ctx, mac, ifName) if err != nil { return nil, err } return d, nil } // updateMsg handles updating dhcpServer packets with the data from the macAndIPXE. func (h *Handler) updateMsg(ctx context.Context, p *data.Packet, d *data.DHCP, msgType dhcpv4.MessageType) *dhcpv4.DHCPv4 { h.setDefaults() mods := []dhcpv4.Modifier{ dhcpv4.WithMessageType(msgType), dhcpv4.WithGeneric(dhcpv4.OptionServerIdentifier, h.IPAddr.AsSlice()), dhcpv4.WithServerIP(d.ServerIP), dhcpv4.WithHwAddr(p.Pkt.ClientHWAddr), } mods = append(mods, h.setDHCPOpts(ctx, p.Pkt, d)...) // We ignore the error here because: // 1. it's only non-nil if the generation of a transaction id (XID) fails. // 2. We always use the clients transaction id (XID) in responses. See dhcpv4.WithReply(). reply, _ := dhcpv4.NewReplyFromRequest(p.Pkt, mods...) log.Printf("%+v", reply) return reply } // hardwareNotFound returns true if the error is from a hardware record not being found. func hardwareNotFound(err error) bool { type hardwareNotFound interface { NotFound() bool } te, ok := err.(hardwareNotFound) return ok && te.NotFound() }