1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
19
"github.com/insomniacslk/dhcp/dhcpv4"
20
"github.com/insomniacslk/dhcp/dhcpv4/server4"
21
"github.com/insomniacslk/dhcp/dhcpv6"
22
"github.com/insomniacslk/dhcp/dhcpv6/server6"
23
"github.com/insomniacslk/dhcp/iana"
24
"github.com/siderolabs/gen/xslices"
25
"golang.org/x/sync/errgroup"
27
"github.com/siderolabs/talos/pkg/provision"
31
func handlerDHCP4(serverIP net.IP, statePath string) server4.Handler {
32
return func(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) {
33
log.Printf("DHCPv4: got %s", m.Summary())
35
if m.OpCode != dhcpv4.OpcodeBootRequest {
39
db, err := LoadIPAMRecords(statePath)
41
log.Printf("failed loading the IPAM db: %s", err)
50
row, ok := db[m.ClientHWAddr.String()]
52
log.Printf("no match for MAC: %s", m.ClientHWAddr.String())
59
log.Printf("no match for MAC on IPv4: %s", m.ClientHWAddr.String())
64
modifiers := []dhcpv4.Modifier{
65
dhcpv4.WithServerIP(serverIP),
66
dhcpv4.WithNetmask(net.CIDRMask(int(match.Netmask), match.IP.BitLen())),
67
dhcpv4.WithYourIP(match.IP.AsSlice()),
68
dhcpv4.WithOption(dhcpv4.OptRouter(match.Gateway.AsSlice())),
69
dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(5 * time.Minute)),
70
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(serverIP)),
73
if m.IsOptionRequested(dhcpv4.OptionDomainNameServer) {
74
modifiers = append(modifiers, dhcpv4.WithOption(dhcpv4.OptDNS(netipAddrsToIPs(match.Nameservers)...)))
77
if match.Hostname != "" && m.IsOptionRequested(dhcpv4.OptionHostName) {
78
modifiers = append(modifiers,
79
dhcpv4.WithOption(dhcpv4.OptHostName(match.Hostname)),
83
resp, err := dhcpv4.NewReplyFromRequest(m,
87
log.Printf("failure building response: %s", err)
92
if m.IsOptionRequested(dhcpv4.OptionBootfileName) {
93
log.Printf("received PXE boot request from %s", m.ClientHWAddr)
94
log.Printf("sending PXE response to %s: %s/%s", m.ClientHWAddr, match.TFTPServer, match.IPXEBootFilename)
96
if match.TFTPServer != "" {
97
resp.ServerIPAddr = net.ParseIP(match.TFTPServer)
98
resp.UpdateOption(dhcpv4.OptTFTPServerName(match.TFTPServer))
101
if match.IPXEBootFilename != "" {
102
resp.UpdateOption(dhcpv4.OptBootFileName(match.IPXEBootFilename))
106
if m.IsOptionRequested(dhcpv4.OptionInterfaceMTU) {
107
resp.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionInterfaceMTU, dhcpv4.Uint16(match.MTU).ToBytes()))
110
switch mt := m.MessageType(); mt { //nolint:exhaustive
111
case dhcpv4.MessageTypeDiscover:
112
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
113
case dhcpv4.MessageTypeRequest, dhcpv4.MessageTypeInform:
114
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
116
log.Printf("unhandled message type: %v", mt)
121
_, err = conn.WriteTo(resp.ToBytes(), peer)
123
log.Printf("failure sending response: %s", err)
129
func handlerDHCP6(serverHwAddr net.HardwareAddr, statePath string) server6.Handler {
130
return func(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) {
131
log.Printf("DHCPv6: got %s", m.Summary())
133
db, err := LoadIPAMRecords(statePath)
135
log.Printf("failed loading the IPAM db: %s", err)
144
msg, err := m.GetInnerMessage()
146
log.Printf("failed loading inner message: %s", err)
151
hwaddr, err := dhcpv6.ExtractMAC(m)
153
log.Printf("error extracting hwaddr: %s", err)
158
row, ok := db[hwaddr.String()]
160
log.Printf("no match for MAC: %s", hwaddr)
167
log.Printf("no match for MAC on IPv6: %s", hwaddr)
172
modifiers := []dhcpv6.Modifier{
173
dhcpv6.WithDNS(netipAddrsToIPs(match.Nameservers)...),
174
dhcpv6.WithIANA(dhcpv6.OptIAAddress{
175
IPv6Addr: match.IP.AsSlice(),
176
PreferredLifetime: 5 * time.Minute,
177
ValidLifetime: 5 * time.Minute,
179
dhcpv6.WithServerID(&dhcpv6.DUIDLLT{
180
HWType: iana.HWTypeEthernet,
181
Time: dhcpv6.GetTime(),
182
LinkLayerAddr: serverHwAddr,
186
if match.Hostname != "" {
187
modifiers = append(modifiers,
188
dhcpv6.WithFQDN(0, match.Hostname),
192
var resp *dhcpv6.Message
194
switch msg.MessageType { //nolint:exhaustive
195
case dhcpv6.MessageTypeSolicit:
196
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg, modifiers...)
197
case dhcpv6.MessageTypeRequest:
198
resp, err = dhcpv6.NewReplyFromMessage(msg, modifiers...)
200
log.Printf("unsupported message type %s", msg.Summary())
204
log.Printf("failure building response: %s", err)
209
_, err = conn.WriteTo(resp.ToBytes(), peer)
211
log.Printf("failure sending response: %s", err)
216
func netipAddrsToIPs(addrs []netip.Addr) []net.IP {
217
return xslices.Map(addrs, func(addr netip.Addr) net.IP {
218
return addr.AsSlice()
223
func DHCPd(ifName string, ips []net.IP, statePath string) error {
224
iface, err := net.InterfaceByName(ifName)
226
return fmt.Errorf("error looking up interface: %w", err)
229
var eg errgroup.Group
231
for _, ip := range ips {
234
server, err := server6.NewServer(
237
handlerDHCP6(iface.HardwareAddr, statePath),
238
server6.WithDebugLogger(),
241
log.Printf("error on dhcp6 startup: %s", err)
246
return server.Serve()
249
server, err := server4.NewServer(
252
handlerDHCP4(ip, statePath),
253
server4.WithSummaryLogger(),
256
log.Printf("error on dhcp4 startup: %s", err)
261
return server.Serve()
269
dhcpPid = "dhcpd.pid"
270
dhcpLog = "dhcpd.log"
273
// CreateDHCPd creates DHCPd.
274
func (p *Provisioner) CreateDHCPd(state *State, clusterReq provision.ClusterRequest) error {
275
pidPath := state.GetRelativePath(dhcpPid)
277
logFile, err := os.OpenFile(state.GetRelativePath(dhcpLog), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o666)
282
defer logFile.Close() //nolint:errcheck
284
statePath, err := state.StatePath()
289
gatewayAddrs := xslices.Map(clusterReq.Network.GatewayAddrs, netip.Addr.String)
293
"--state-path", statePath,
294
"--addr", strings.Join(gatewayAddrs, ","),
295
"--interface", state.BridgeName,
296
"--ipxe-next-handler", clusterReq.IPXEBootScript,
299
cmd := exec.Command(clusterReq.SelfExecutable, args...)
302
cmd.SysProcAttr = &syscall.SysProcAttr{
303
Setsid: true, // daemonize
306
if err = cmd.Start(); err != nil {
310
if err = os.WriteFile(pidPath, []byte(strconv.Itoa(cmd.Process.Pid)), os.ModePerm); err != nil {
311
return fmt.Errorf("error writing dhcp PID file: %w", err)
317
// DestroyDHCPd destoys load balancer.
318
func (p *Provisioner) DestroyDHCPd(state *State) error {
319
pidPath := state.GetRelativePath(dhcpPid)
321
return StopProcessByPidfile(pidPath)