talos

Форк
0
322 строки · 7.4 Кб
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/.
4

5
package vm
6

7
import (
8
	"fmt"
9
	"log"
10
	"net"
11
	"net/netip"
12
	"os"
13
	"os/exec"
14
	"strconv"
15
	"strings"
16
	"syscall"
17
	"time"
18

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"
26

27
	"github.com/siderolabs/talos/pkg/provision"
28
)
29

30
//nolint:gocyclo
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())
34

35
		if m.OpCode != dhcpv4.OpcodeBootRequest {
36
			return
37
		}
38

39
		db, err := LoadIPAMRecords(statePath)
40
		if err != nil {
41
			log.Printf("failed loading the IPAM db: %s", err)
42

43
			return
44
		}
45

46
		if db == nil {
47
			return
48
		}
49

50
		row, ok := db[m.ClientHWAddr.String()]
51
		if !ok {
52
			log.Printf("no match for MAC: %s", m.ClientHWAddr.String())
53

54
			return
55
		}
56

57
		match, ok := row[4]
58
		if !ok {
59
			log.Printf("no match for MAC on IPv4: %s", m.ClientHWAddr.String())
60

61
			return
62
		}
63

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)),
71
		}
72

73
		if m.IsOptionRequested(dhcpv4.OptionDomainNameServer) {
74
			modifiers = append(modifiers, dhcpv4.WithOption(dhcpv4.OptDNS(netipAddrsToIPs(match.Nameservers)...)))
75
		}
76

77
		if match.Hostname != "" && m.IsOptionRequested(dhcpv4.OptionHostName) {
78
			modifiers = append(modifiers,
79
				dhcpv4.WithOption(dhcpv4.OptHostName(match.Hostname)),
80
			)
81
		}
82

83
		resp, err := dhcpv4.NewReplyFromRequest(m,
84
			modifiers...,
85
		)
86
		if err != nil {
87
			log.Printf("failure building response: %s", err)
88

89
			return
90
		}
91

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)
95

96
			if match.TFTPServer != "" {
97
				resp.ServerIPAddr = net.ParseIP(match.TFTPServer)
98
				resp.UpdateOption(dhcpv4.OptTFTPServerName(match.TFTPServer))
99
			}
100

101
			if match.IPXEBootFilename != "" {
102
				resp.UpdateOption(dhcpv4.OptBootFileName(match.IPXEBootFilename))
103
			}
104
		}
105

106
		if m.IsOptionRequested(dhcpv4.OptionInterfaceMTU) {
107
			resp.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionInterfaceMTU, dhcpv4.Uint16(match.MTU).ToBytes()))
108
		}
109

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))
115
		default:
116
			log.Printf("unhandled message type: %v", mt)
117

118
			return
119
		}
120

121
		_, err = conn.WriteTo(resp.ToBytes(), peer)
122
		if err != nil {
123
			log.Printf("failure sending response: %s", err)
124
		}
125
	}
126
}
127

128
//nolint:gocyclo
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())
132

133
		db, err := LoadIPAMRecords(statePath)
134
		if err != nil {
135
			log.Printf("failed loading the IPAM db: %s", err)
136

137
			return
138
		}
139

140
		if db == nil {
141
			return
142
		}
143

144
		msg, err := m.GetInnerMessage()
145
		if err != nil {
146
			log.Printf("failed loading inner message: %s", err)
147

148
			return
149
		}
150

151
		hwaddr, err := dhcpv6.ExtractMAC(m)
152
		if err != nil {
153
			log.Printf("error extracting hwaddr: %s", err)
154

155
			return
156
		}
157

158
		row, ok := db[hwaddr.String()]
159
		if !ok {
160
			log.Printf("no match for MAC: %s", hwaddr)
161

162
			return
163
		}
164

165
		match, ok := row[6]
166
		if !ok {
167
			log.Printf("no match for MAC on IPv6: %s", hwaddr)
168

169
			return
170
		}
171

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,
178
			}),
179
			dhcpv6.WithServerID(&dhcpv6.DUIDLLT{
180
				HWType:        iana.HWTypeEthernet,
181
				Time:          dhcpv6.GetTime(),
182
				LinkLayerAddr: serverHwAddr,
183
			}),
184
		}
185

186
		if match.Hostname != "" {
187
			modifiers = append(modifiers,
188
				dhcpv6.WithFQDN(0, match.Hostname),
189
			)
190
		}
191

192
		var resp *dhcpv6.Message
193

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...)
199
		default:
200
			log.Printf("unsupported message type %s", msg.Summary())
201
		}
202

203
		if err != nil {
204
			log.Printf("failure building response: %s", err)
205

206
			return
207
		}
208

209
		_, err = conn.WriteTo(resp.ToBytes(), peer)
210
		if err != nil {
211
			log.Printf("failure sending response: %s", err)
212
		}
213
	}
214
}
215

216
func netipAddrsToIPs(addrs []netip.Addr) []net.IP {
217
	return xslices.Map(addrs, func(addr netip.Addr) net.IP {
218
		return addr.AsSlice()
219
	})
220
}
221

222
// DHCPd entrypoint.
223
func DHCPd(ifName string, ips []net.IP, statePath string) error {
224
	iface, err := net.InterfaceByName(ifName)
225
	if err != nil {
226
		return fmt.Errorf("error looking up interface: %w", err)
227
	}
228

229
	var eg errgroup.Group
230

231
	for _, ip := range ips {
232
		eg.Go(func() error {
233
			if ip.To4() == nil {
234
				server, err := server6.NewServer(
235
					ifName,
236
					nil,
237
					handlerDHCP6(iface.HardwareAddr, statePath),
238
					server6.WithDebugLogger(),
239
				)
240
				if err != nil {
241
					log.Printf("error on dhcp6 startup: %s", err)
242

243
					return err
244
				}
245

246
				return server.Serve()
247
			}
248

249
			server, err := server4.NewServer(
250
				ifName,
251
				nil,
252
				handlerDHCP4(ip, statePath),
253
				server4.WithSummaryLogger(),
254
			)
255
			if err != nil {
256
				log.Printf("error on dhcp4 startup: %s", err)
257

258
				return err
259
			}
260

261
			return server.Serve()
262
		})
263
	}
264

265
	return eg.Wait()
266
}
267

268
const (
269
	dhcpPid = "dhcpd.pid"
270
	dhcpLog = "dhcpd.log"
271
)
272

273
// CreateDHCPd creates DHCPd.
274
func (p *Provisioner) CreateDHCPd(state *State, clusterReq provision.ClusterRequest) error {
275
	pidPath := state.GetRelativePath(dhcpPid)
276

277
	logFile, err := os.OpenFile(state.GetRelativePath(dhcpLog), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0o666)
278
	if err != nil {
279
		return err
280
	}
281

282
	defer logFile.Close() //nolint:errcheck
283

284
	statePath, err := state.StatePath()
285
	if err != nil {
286
		return err
287
	}
288

289
	gatewayAddrs := xslices.Map(clusterReq.Network.GatewayAddrs, netip.Addr.String)
290

291
	args := []string{
292
		"dhcpd-launch",
293
		"--state-path", statePath,
294
		"--addr", strings.Join(gatewayAddrs, ","),
295
		"--interface", state.BridgeName,
296
		"--ipxe-next-handler", clusterReq.IPXEBootScript,
297
	}
298

299
	cmd := exec.Command(clusterReq.SelfExecutable, args...)
300
	cmd.Stdout = logFile
301
	cmd.Stderr = logFile
302
	cmd.SysProcAttr = &syscall.SysProcAttr{
303
		Setsid: true, // daemonize
304
	}
305

306
	if err = cmd.Start(); err != nil {
307
		return err
308
	}
309

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)
312
	}
313

314
	return nil
315
}
316

317
// DestroyDHCPd destoys load balancer.
318
func (p *Provisioner) DestroyDHCPd(state *State) error {
319
	pidPath := state.GetRelativePath(dhcpPid)
320

321
	return StopProcessByPidfile(pidPath)
322
}
323

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.