proxy.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /*
  2. Package proxy implements a dhcpServer handler that provides proxyDHCP functionality.
  3. "[A] Proxy dhcpServer server behaves much like a dhcpServer server by listening for ordinary
  4. dhcpServer client traffic and responding to certain client requests. However, unlike the
  5. dhcpServer server, the PXE Proxy dhcpServer server does not administer network addresses, and
  6. it only responds to clients that identify themselves as PXE clients. The responses
  7. given by the PXE Proxy dhcpServer server contain the mechanism by which the client locates
  8. the boot servers or the network addresses and descriptions of the supported,
  9. compatible boot servers."
  10. Reference: https://www.ibm.com/docs/en/aix/7.1?topic=protocol-preboot-execution-environment-proxy-dhcp-daemon
  11. */
  12. package proxy
  13. import (
  14. "context"
  15. "dhcp/data"
  16. "dhcp/handler"
  17. "errors"
  18. "fmt"
  19. "net"
  20. "net/netip"
  21. "github.com/go-logr/logr"
  22. "github.com/insomniacslk/dhcp/dhcpv4"
  23. "golang.org/x/net/ipv4"
  24. )
  25. // Handler holds the configuration details for the running the dhcpServer server.
  26. type Handler struct {
  27. // Backend is the macAndIPXE to use for getting dhcpServer data.
  28. Backend handler.BackendReader
  29. // IPAddr is the IP address to use in dhcpServer responses.
  30. // Option 54 and the sname dhcpServer header.
  31. // This could be a load balancer IP address or an ingress IP address or a local IP address.
  32. IPAddr netip.Addr
  33. // Log is used to log messages.
  34. // `logr.Discard()` can be used if no logging is desired.
  35. Log logr.Logger
  36. }
  37. // Redirection name comes from section 2.5 of http://www.pix.net/software/pxeboot/archive/pxespec.pdf
  38. func (h *Handler) Handle(ctx context.Context, conn *ipv4.PacketConn, dp data.Packet) {
  39. // validations
  40. if dp.Pkt == nil {
  41. h.Log.Error(errors.New("incoming packet is nil"), "not able to respond when the incoming packet is nil")
  42. return
  43. }
  44. upeer, ok := dp.Peer.(*net.UDPAddr)
  45. if !ok {
  46. h.Log.Error(errors.New("peer is not a UDP connection"), "not able to respond when the peer is not a UDP connection")
  47. return
  48. }
  49. if upeer == nil {
  50. h.Log.Error(errors.New("peer is nil"), "not able to respond when the peer is nil")
  51. return
  52. }
  53. if conn == nil {
  54. h.Log.Error(errors.New("connection is nil"), "not able to respond when the connection is nil")
  55. return
  56. }
  57. var ifName string
  58. if dp.Md != nil {
  59. ifName = dp.Md.IfName
  60. }
  61. log := h.Log.WithValues("mac", dp.Pkt.ClientHWAddr.String(), "xid", dp.Pkt.TransactionID.String(), "interface", ifName)
  62. // We ignore the error here because:
  63. // 1. it's only non-nil if the generation of a transaction id (XID) fails.
  64. // 2. We always use the clients transaction id (XID) in responses. See dhcpv4.WithReply().
  65. reply, _ := dhcpv4.NewReplyFromRequest(dp.Pkt)
  66. 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
  67. log.V(1).Info("Ignoring packet", "OpCode", dp.Pkt.OpCode)
  68. return
  69. }
  70. if err := setMessageType(reply, dp.Pkt.MessageType()); err != nil {
  71. log.V(1).Info("Ignoring packet", "error", err.Error())
  72. return
  73. }
  74. // Set option 97
  75. reply.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionClientMachineIdentifier, dp.Pkt.GetOneOption(dhcpv4.OptionClientMachineIdentifier)))
  76. log.Info(
  77. "received dhcpServer packet",
  78. "type", dp.Pkt.MessageType().String(),
  79. )
  80. dst := replyDestination(dp.Peer, dp.Pkt.GatewayIPAddr)
  81. cm := &ipv4.ControlMessage{}
  82. if dp.Md != nil {
  83. cm.IfIndex = dp.Md.IfIndex
  84. }
  85. log = log.WithValues(
  86. "destination", dst.String(),
  87. "bootFileName", reply.BootFileName,
  88. "nextServer", reply.ServerIPAddr.String(),
  89. "messageType", reply.MessageType().String(),
  90. "serverHostname", reply.ServerHostName,
  91. )
  92. // send the dhcpServer packet
  93. if _, err := conn.WriteTo(reply.ToBytes(), cm, dst); err != nil {
  94. log.Error(err, "failed to send ProxyDHCP response")
  95. return
  96. }
  97. log.Info("Sent ProxyDHCP response")
  98. }
  99. func setMessageType(reply *dhcpv4.DHCPv4, reqMsg dhcpv4.MessageType) error {
  100. switch mt := reqMsg; mt {
  101. case dhcpv4.MessageTypeDiscover:
  102. reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
  103. case dhcpv4.MessageTypeRequest:
  104. reply.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
  105. default:
  106. return IgnorePacketError{PacketType: mt, Details: "proxyDHCP only responds to Discover or Request message types"}
  107. }
  108. return nil
  109. }
  110. // IgnorePacketError is for when a dhcpServer packet should be ignored.
  111. type IgnorePacketError struct {
  112. PacketType dhcpv4.MessageType
  113. Details string
  114. }
  115. // Error returns the string representation of ErrIgnorePacket.
  116. func (e IgnorePacketError) Error() string {
  117. return fmt.Sprintf("Ignoring packet: message type %s: details %s", e.PacketType, e.Details)
  118. }
  119. // replyDestination determines the destination address for the dhcpServer reply.
  120. // If the giaddr is set, then the reply should be sent to the giaddr.
  121. // Otherwise, the reply should be sent to the direct peer.
  122. //
  123. // From page 22 of https://www.ietf.org/rfc/rfc2131.txt:
  124. // "If the 'giaddr' field in a dhcpServer message from a client is non-zero,
  125. // the server sends any return messages to the 'dhcpServer server' port on
  126. // the BOOTP relay agent whose address appears in 'giaddr'.".
  127. func replyDestination(directPeer net.Addr, giaddr net.IP) net.Addr {
  128. if !giaddr.IsUnspecified() && giaddr != nil {
  129. return &net.UDPAddr{IP: giaddr, Port: dhcpv4.ServerPort}
  130. }
  131. return directPeer
  132. }