123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- /*
- Package proxy implements a dhcpServer handler that provides proxyDHCP functionality.
- "[A] Proxy dhcpServer server behaves much like a dhcpServer server by listening for ordinary
- dhcpServer client traffic and responding to certain client requests. However, unlike the
- dhcpServer server, the PXE Proxy dhcpServer server does not administer network addresses, and
- it only responds to clients that identify themselves as PXE clients. The responses
- given by the PXE Proxy dhcpServer server contain the mechanism by which the client locates
- the boot servers or the network addresses and descriptions of the supported,
- compatible boot servers."
- Reference: https://www.ibm.com/docs/en/aix/7.1?topic=protocol-preboot-execution-environment-proxy-dhcp-daemon
- */
- package proxy
- import (
- "context"
- "dhcp/data"
- "dhcp/handler"
- "errors"
- "fmt"
- "net"
- "net/netip"
- "github.com/go-logr/logr"
- "github.com/insomniacslk/dhcp/dhcpv4"
- "golang.org/x/net/ipv4"
- )
- // Handler holds the configuration details for the running the dhcpServer server.
- type Handler struct {
- // Backend is the macAndIPXE to use for getting dhcpServer data.
- Backend handler.BackendReader
- // IPAddr is the IP address to use in dhcpServer responses.
- // Option 54 and the sname dhcpServer header.
- // This could be a load balancer IP address or an ingress IP address or a local IP address.
- IPAddr netip.Addr
- // Log is used to log messages.
- // `logr.Discard()` can be used if no logging is desired.
- Log logr.Logger
- }
- // Redirection name comes from section 2.5 of http://www.pix.net/software/pxeboot/archive/pxespec.pdf
- func (h *Handler) Handle(ctx context.Context, conn *ipv4.PacketConn, dp data.Packet) {
- // validations
- if dp.Pkt == nil {
- h.Log.Error(errors.New("incoming packet is nil"), "not able to respond when the incoming packet is nil")
- return
- }
- upeer, ok := dp.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 dp.Md != nil {
- ifName = dp.Md.IfName
- }
- log := h.Log.WithValues("mac", dp.Pkt.ClientHWAddr.String(), "xid", dp.Pkt.TransactionID.String(), "interface", ifName)
- // 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(dp.Pkt)
- if dp.Pkt.OpCode != dhcpv4.OpcodeBootRequest { // TODO(jacobweinstock): dont understand this, found it in an example here: https://github.com/insomniacslk/dhcp/blob/c51060810aaab9c8a0bd1b0fcbf72bc0b91e6427/dhcpv4/server4/server_test.go#L31
- log.V(1).Info("Ignoring packet", "OpCode", dp.Pkt.OpCode)
- return
- }
- if err := setMessageType(reply, dp.Pkt.MessageType()); err != nil {
- log.V(1).Info("Ignoring packet", "error", err.Error())
- return
- }
- // Set option 97
- reply.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionClientMachineIdentifier, dp.Pkt.GetOneOption(dhcpv4.OptionClientMachineIdentifier)))
- log.Info(
- "received dhcpServer packet",
- "type", dp.Pkt.MessageType().String(),
- )
- dst := replyDestination(dp.Peer, dp.Pkt.GatewayIPAddr)
- cm := &ipv4.ControlMessage{}
- if dp.Md != nil {
- cm.IfIndex = dp.Md.IfIndex
- }
- log = log.WithValues(
- "destination", dst.String(),
- "bootFileName", reply.BootFileName,
- "nextServer", reply.ServerIPAddr.String(),
- "messageType", reply.MessageType().String(),
- "serverHostname", reply.ServerHostName,
- )
- // send the dhcpServer packet
- if _, err := conn.WriteTo(reply.ToBytes(), cm, dst); err != nil {
- log.Error(err, "failed to send ProxyDHCP response")
- return
- }
- log.Info("Sent ProxyDHCP response")
- }
- func setMessageType(reply *dhcpv4.DHCPv4, reqMsg dhcpv4.MessageType) error {
- switch mt := reqMsg; mt {
- case dhcpv4.MessageTypeDiscover:
- reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
- case dhcpv4.MessageTypeRequest:
- reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
- default:
- return IgnorePacketError{PacketType: mt, Details: "proxyDHCP only responds to Discover or Request message types"}
- }
- return nil
- }
- // IgnorePacketError is for when a dhcpServer packet should be ignored.
- type IgnorePacketError struct {
- PacketType dhcpv4.MessageType
- Details string
- }
- // Error returns the string representation of ErrIgnorePacket.
- func (e IgnorePacketError) Error() string {
- return fmt.Sprintf("Ignoring packet: message type %s: details %s", e.PacketType, e.Details)
- }
- // 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
- }
|