Skip to content

Commit 706b40b

Browse files
rahafjrwstgraber
authored andcommitted
incusd/main_forknet: Implement stateful DHCPv6
Signed-off-by: Rahaf Aljerwi <[email protected]>
1 parent 59fb539 commit 706b40b

File tree

1 file changed

+214
-54
lines changed

1 file changed

+214
-54
lines changed

cmd/incusd/main_forknet.go

Lines changed: 214 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,13 @@ import (
219219
"os"
220220
"path/filepath"
221221
"strings"
222+
"sync"
222223
"time"
223224

224225
"github.com/insomniacslk/dhcp/dhcpv4"
225226
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
227+
"github.com/insomniacslk/dhcp/dhcpv6"
228+
"github.com/insomniacslk/dhcp/dhcpv6/nclient6"
226229
"github.com/sirupsen/logrus"
227230
"github.com/spf13/cobra"
228231

@@ -234,7 +237,11 @@ import (
234237
)
235238

236239
type cmdForknet struct {
237-
global *cmdGlobal
240+
global *cmdGlobal
241+
dhcpv4Lease *nclient4.Lease
242+
dhcpv6Lease *dhcpv6.Message
243+
applyDNSMu sync.Mutex
244+
args []string
238245
}
239246

240247
func (c *cmdForknet) command() *cobra.Command {
@@ -298,6 +305,7 @@ func (c *cmdForknet) runInfo(_ *cobra.Command, _ []string) error {
298305
func (c *cmdForknet) runDHCP(_ *cobra.Command, args []string) error {
299306
logger := logrus.New()
300307
logger.Level = logrus.DebugLevel
308+
c.args = args
301309

302310
if C.forknet_dhcp_logfile >= 0 {
303311
logger.SetOutput(os.NewFile(uintptr(C.forknet_dhcp_logfile), "incus-dhcp-logfile"))
@@ -317,7 +325,7 @@ func (c *cmdForknet) runDHCP(_ *cobra.Command, args []string) error {
317325
err := link.SetUp()
318326
if err != nil {
319327
logger.WithField("interface", iface).Error("Giving up on DHCP, couldn't bring up interface")
320-
return nil
328+
return err
321329
}
322330

323331
// Read the hostname.
@@ -328,67 +336,62 @@ func (c *cmdForknet) runDHCP(_ *cobra.Command, args []string) error {
328336

329337
hostname := strings.TrimSpace(string(bb))
330338

339+
// Create PID file.
340+
err = os.WriteFile(filepath.Join(args[0], "dhcp.pid"), []byte(fmt.Sprintf("%d", os.Getpid())), 0o644)
341+
if err != nil {
342+
logger.WithError(err).Error("Giving up on DHCP, couldn't write PID file")
343+
return err
344+
}
345+
346+
errorChannel := make(chan error, 2)
347+
go c.dhcpRunV4(errorChannel, iface, hostname, logger)
348+
go c.dhcpRunV6(errorChannel, iface, hostname, logger)
349+
350+
err = <-errorChannel
351+
return err
352+
}
353+
354+
func (c *cmdForknet) dhcpRunV4(errorChannel chan error, iface string, hostname string, logger *logrus.Logger) {
331355
// Try to get a lease.
332356
client, err := nclient4.New(iface)
333357
if err != nil {
334-
logger.WithError(err).Error("Giving up on DHCP, couldn't set up client")
335-
return nil
358+
logger.WithError(err).Error("Giving up on DHCPv4, couldn't set up client")
359+
errorChannel <- err
360+
return
336361
}
337362

338363
defer func() { _ = client.Close() }()
339364

340365
lease, err := client.Request(context.Background(), dhcpv4.WithOption(dhcpv4.OptHostName(hostname)))
341366
if err != nil {
342367
logger.WithError(err).WithField("hostname", hostname).
343-
Error("Giving up on DHCP, couldn't get a lease")
344-
return nil
368+
Error("Giving up on DHCPv4, couldn't get a lease")
369+
errorChannel <- err
370+
return
345371
}
346372

347373
// Parse the response.
348374
if lease.Offer == nil {
349375
logger.WithField("hostname", hostname).
350376
Error("Giving up on DHCP, couldn't get a lease after 5s")
351-
return nil
377+
errorChannel <- err
378+
return
352379
}
353380

354381
if lease.Offer.YourIPAddr == nil || lease.Offer.YourIPAddr.Equal(net.IPv4zero) || lease.Offer.SubnetMask() == nil || len(lease.Offer.Router()) != 1 {
355382
logger.Error("Giving up on DHCP, lease didn't contain required fields")
356-
return nil
383+
errorChannel <- fmt.Errorf("Giving up on DHCP, lease didn't contain required fields")
384+
return
357385
}
358386

359-
if len(lease.Offer.DNS()) > 0 {
360-
// DNS configuration.
361-
f, err := os.Create(filepath.Join(args[0], "resolv.conf"))
362-
if err != nil {
363-
logger.WithError(err).Error("Giving up on DHCP, couldn't create resolv.conf")
364-
return nil
365-
}
366-
367-
defer f.Close()
368-
369-
for _, nameserver := range lease.Offer.DNS() {
370-
_, err = fmt.Fprintf(f, "nameserver %s\n", nameserver)
371-
if err != nil {
372-
logger.WithError(err).Error("Giving up on DHCP, couldn't prepare resolv.conf")
373-
return nil
374-
}
375-
}
376-
377-
if lease.Offer.DomainName() != "" {
378-
_, err = fmt.Fprintf(f, "domain %s\n", lease.Offer.DomainName())
379-
if err != nil {
380-
logger.WithError(err).Error("Giving up on DHCP, couldn't prepare resolv.conf")
381-
return nil
382-
}
383-
}
384-
385-
if lease.Offer.DomainSearch() != nil && len(lease.Offer.DomainSearch().Labels) > 0 {
386-
_, err = fmt.Fprintf(f, "search %s\n", strings.Join(lease.Offer.DomainSearch().Labels, ", "))
387-
if err != nil {
388-
logger.WithError(err).Error("Giving up on DHCP, couldn't prepare resolv.conf")
389-
return nil
390-
}
391-
}
387+
c.applyDNSMu.Lock()
388+
c.dhcpv4Lease = lease
389+
c.applyDNSMu.Unlock()
390+
err = c.dhcpApplyDNS(logger)
391+
if err != nil {
392+
logger.WithError(err).Error("Giving up on DHCPv4, error applying DNS")
393+
errorChannel <- err
394+
return
392395
}
393396

394397
// Network configuration.
@@ -402,8 +405,9 @@ func (c *cmdForknet) runDHCP(_ *cobra.Command, args []string) error {
402405

403406
err = addr.Add()
404407
if err != nil {
405-
logger.WithError(err).Error("Giving up on DHCP, couldn't add IP")
406-
return nil
408+
logger.WithError(err).Error("Giving up on DHCPv4, couldn't add IP")
409+
errorChannel <- err
410+
return
407411
}
408412

409413
if lease.Offer.Options.Has(dhcpv4.OptionClasslessStaticRoute) {
@@ -421,7 +425,8 @@ func (c *cmdForknet) runDHCP(_ *cobra.Command, args []string) error {
421425
err = route.Add()
422426
if err != nil {
423427
logger.WithError(err).Error("Giving up on DHCP, couldn't add classless static route")
424-
return nil
428+
errorChannel <- err
429+
return
425430
}
426431
}
427432
} else {
@@ -435,17 +440,11 @@ func (c *cmdForknet) runDHCP(_ *cobra.Command, args []string) error {
435440
err = route.Add()
436441
if err != nil {
437442
logger.WithError(err).Error("Giving up on DHCP, couldn't add default route")
438-
return nil
443+
errorChannel <- err
444+
return
439445
}
440446
}
441447

442-
// Create PID file.
443-
err = os.WriteFile(filepath.Join(args[0], "dhcp.pid"), []byte(fmt.Sprintf("%d", os.Getpid())), 0o644)
444-
if err != nil {
445-
logger.WithError(err).Error("Giving up on DHCP, couldn't write PID file")
446-
return nil
447-
}
448-
449448
// Handle DHCP renewal.
450449
for {
451450
// Wait until it's renewal time.
@@ -454,14 +453,175 @@ func (c *cmdForknet) runDHCP(_ *cobra.Command, args []string) error {
454453
// Renew the lease.
455454
newLease, err := client.Renew(context.Background(), lease, dhcpv4.WithOption(dhcpv4.OptHostName(hostname)))
456455
if err != nil {
457-
logger.WithError(err).Error("Giving up on DHCP, couldn't renew the lease")
458-
return nil
456+
logger.WithError(err).Error("Giving up on DHCPv4, couldn't renew the lease")
457+
errorChannel <- err
458+
return
459459
}
460460

461461
lease = newLease
462462
}
463463
}
464464

465+
func (c *cmdForknet) dhcpRunV6(errorChannel chan error, iface string, hostname string, logger *logrus.Logger) {
466+
// Get a new DHCPv6 client.
467+
client, err := nclient6.New(iface)
468+
if err != nil {
469+
logger.WithError(err).Error("Giving up on DHCPv6, couldn't set up client")
470+
errorChannel <- err
471+
return
472+
}
473+
474+
defer func() { _ = client.Close() }()
475+
476+
// Try to get a lease.
477+
advertisement, err := client.Solicit(context.Background(), dhcpv6.WithFQDN(0, hostname))
478+
if err != nil {
479+
logger.WithError(err).Error("Giving up on DHCPv6, error during DHCPv6 Solicit")
480+
errorChannel <- err
481+
return
482+
}
483+
484+
reply, err := client.Request(context.Background(), advertisement, dhcpv6.WithFQDN(0, hostname))
485+
if err != nil {
486+
logger.WithError(err).Error("Giving up on DHCPv6, error during DHCPv6 Request")
487+
errorChannel <- err
488+
return
489+
}
490+
491+
c.applyDNSMu.Lock()
492+
c.dhcpv6Lease = reply
493+
c.applyDNSMu.Unlock()
494+
err = c.dhcpApplyDNS(logger)
495+
if err != nil {
496+
logger.WithError(err).Error("Giving up on DHCPv6, error applying DNS")
497+
errorChannel <- err
498+
return
499+
}
500+
501+
// Network configuration.
502+
iana := reply.Options.OneIANA()
503+
if iana == nil {
504+
logger.Error("Giving up on DHCPv6 renewal, reply missing IANA")
505+
errorChannel <- fmt.Errorf("Giving up on DHCPv6 renewal, reply missing IANA")
506+
return
507+
}
508+
509+
for _, iaaddr := range iana.Options.Addresses() {
510+
addr := &ip.Addr{
511+
DevName: iface,
512+
Address: fmt.Sprintf("%s/%d", iaaddr.IPv6Addr, net.CIDRMask(128, 128)),
513+
Family: ip.FamilyV6,
514+
}
515+
516+
err = addr.Add()
517+
if err != nil {
518+
logger.WithError(err).Error("Giving up on DHCPv6, couldn't add IP")
519+
errorChannel <- err
520+
return
521+
}
522+
}
523+
524+
// Handle DHCP Renewal.
525+
for {
526+
// Wait until it's renewal time.
527+
t1 := iana.T1
528+
time.Sleep(t1)
529+
530+
// Renew the lease.
531+
var optIAAddrs []dhcpv6.OptIAAddress
532+
for _, optIAAddr := range iana.Options.Addresses() {
533+
optIAAddrs = append(optIAAddrs, *optIAAddr)
534+
}
535+
536+
modifiers := []dhcpv6.Modifier{
537+
dhcpv6.WithClientID(reply.Options.ClientID()),
538+
dhcpv6.WithServerID(reply.Options.ServerID()),
539+
dhcpv6.WithIAID(iana.IaId),
540+
dhcpv6.WithIANA(optIAAddrs...),
541+
}
542+
543+
renew, err := dhcpv6.NewMessage(modifiers...)
544+
if err != nil {
545+
logger.WithError(err).Error("Giving up on DHCv6, couldn't create renew message")
546+
errorChannel <- err
547+
return
548+
}
549+
550+
renew.MessageType = dhcpv6.MessageTypeRenew
551+
552+
newReply, err := dhcpv6.NewReplyFromMessage(renew, dhcpv6.WithFQDN(0, hostname))
553+
if err != nil {
554+
logger.WithError(err).Error("Giving up on DHCPv6, couldn't renew the lease")
555+
errorChannel <- err
556+
return
557+
}
558+
559+
reply = newReply
560+
}
561+
}
562+
563+
func (c *cmdForknet) dhcpApplyDNS(logger *logrus.Logger) error {
564+
c.applyDNSMu.Lock()
565+
defer c.applyDNSMu.Unlock()
566+
567+
f, err := os.Create(filepath.Join(c.args[0], "resolv.conf"))
568+
if err != nil {
569+
logger.WithError(err).Error("Giving up on DHCP, couldn't create resolv.conf")
570+
return err
571+
}
572+
573+
defer f.Close()
574+
575+
if c.dhcpv4Lease != nil {
576+
if len(c.dhcpv4Lease.Offer.DNS()) > 0 {
577+
for _, nameserver := range c.dhcpv4Lease.Offer.DNS() {
578+
_, err = fmt.Fprintf(f, "nameserver %s\n", nameserver)
579+
if err != nil {
580+
logger.WithError(err).Error("Giving up on DHCPv4, couldn't prepare resolv.conf")
581+
return err
582+
}
583+
}
584+
585+
if c.dhcpv4Lease.Offer.DomainName() != "" {
586+
_, err = fmt.Fprintf(f, "domain %s\n", c.dhcpv4Lease.Offer.DomainName())
587+
if err != nil {
588+
logger.WithError(err).Error("Giving up on DHCPv4, couldn't prepare resolv.conf")
589+
return err
590+
}
591+
}
592+
593+
if c.dhcpv4Lease.Offer.DomainSearch() != nil && len(c.dhcpv4Lease.Offer.DomainSearch().Labels) > 0 {
594+
_, err = fmt.Fprintf(f, "search %s\n", strings.Join(c.dhcpv4Lease.Offer.DomainSearch().Labels, ", "))
595+
if err != nil {
596+
logger.WithError(err).Error("Giving up on DHCPv4, couldn't prepare resolv.conf")
597+
return err
598+
}
599+
}
600+
}
601+
}
602+
603+
if c.dhcpv6Lease != nil {
604+
if len(c.dhcpv6Lease.Options.DNS()) > 0 {
605+
for _, nameserver := range c.dhcpv6Lease.Options.DNS() {
606+
_, err = fmt.Fprintf(f, "nameserver %s\n", nameserver)
607+
if err != nil {
608+
logger.WithError(err).Error("Giving up on DHCPv6, couldn't prepare resolv.conf")
609+
return err
610+
}
611+
}
612+
613+
if c.dhcpv6Lease.Options.DomainSearchList() != nil && len(c.dhcpv6Lease.Options.DomainSearchList().Labels) > 0 {
614+
_, err = fmt.Fprintf(f, "search %s\n", strings.Join(c.dhcpv6Lease.Options.DomainSearchList().Labels, ", "))
615+
if err != nil {
616+
logger.WithError(err).Error("Giving up on DHCPv6, couldn't prepare resolv.conf")
617+
return err
618+
}
619+
}
620+
}
621+
}
622+
return nil
623+
}
624+
465625
func (c *cmdForknet) runDetach(_ *cobra.Command, args []string) error {
466626
daemonPID := args[1]
467627
ifName := args[2]

0 commit comments

Comments
 (0)