-
Notifications
You must be signed in to change notification settings - Fork 517
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement IPv6 DHCP client in networkd
This renames existing 'DHCP' implementation to `DHCP4`, new client is `DHCP6`. For now, `DHCP6` is disabled by default and should be explicitly enabled with the config. QEMU testbed for IPv6 is going to be pushed as separate PR. Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
- Loading branch information
Showing
11 changed files
with
297 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
package address | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"net" | ||
"strings" | ||
"time" | ||
|
||
"github.com/insomniacslk/dhcp/dhcpv6" | ||
"github.com/insomniacslk/dhcp/dhcpv6/nclient6" | ||
"github.com/jsimonetti/rtnetlink" | ||
"github.com/talos-systems/go-retry/retry" | ||
"golang.org/x/sys/unix" | ||
) | ||
|
||
// DHCP6 implements the Addressing interface. | ||
type DHCP6 struct { | ||
Reply *dhcpv6.Message | ||
NetIf *net.Interface | ||
Mtu int | ||
} | ||
|
||
// Name returns back the name of the address method. | ||
func (d *DHCP6) Name() string { | ||
return "dhcp6" | ||
} | ||
|
||
// Link returns the underlying net.Interface that this address | ||
// method is configured for. | ||
func (d *DHCP6) Link() *net.Interface { | ||
return d.NetIf | ||
} | ||
|
||
// Discover handles the DHCP client exchange stores the DHCP Ack. | ||
func (d *DHCP6) Discover(ctx context.Context, link *net.Interface) error { | ||
d.NetIf = link | ||
err := d.discover(ctx) | ||
|
||
return err | ||
} | ||
|
||
// Address returns back the IP address from the received DHCP offer. | ||
func (d *DHCP6) Address() *net.IPNet { | ||
if d.Reply.Options.OneIANA() == nil { | ||
return nil | ||
} | ||
|
||
return &net.IPNet{ | ||
IP: d.Reply.Options.OneIANA().Options.OneAddress().IPv6Addr, | ||
Mask: net.CIDRMask(128, 128), | ||
} | ||
} | ||
|
||
// Mask returns the netmask from the DHCP offer. | ||
func (d *DHCP6) Mask() net.IPMask { | ||
return net.CIDRMask(128, 128) | ||
} | ||
|
||
// MTU returs the MTU size from the DHCP offer. | ||
func (d *DHCP6) MTU() uint32 { | ||
if d.Mtu > 0 { | ||
return uint32(d.Mtu) | ||
} | ||
|
||
return uint32(d.NetIf.MTU) | ||
} | ||
|
||
// TTL denotes how long a DHCP offer is valid for. | ||
func (d *DHCP6) TTL() time.Duration { | ||
if d.Reply == nil { | ||
return 0 | ||
} | ||
|
||
return d.Reply.Options.OneIANA().Options.OneAddress().ValidLifetime | ||
} | ||
|
||
// Family qualifies the address as ipv4 or ipv6. | ||
func (d *DHCP6) Family() int { | ||
return unix.AF_INET6 | ||
} | ||
|
||
// Scope sets the address scope. | ||
func (d *DHCP6) Scope() uint8 { | ||
return unix.RT_SCOPE_UNIVERSE | ||
} | ||
|
||
// Valid denotes if this address method should be used. | ||
func (d *DHCP6) Valid() bool { | ||
return d.Reply != nil && d.Reply.Options.OneIANA() != nil | ||
} | ||
|
||
// Routes is not supported on IPv6. | ||
func (d *DHCP6) Routes() (routes []*Route) { | ||
return nil | ||
} | ||
|
||
// Resolvers returns the DNS resolvers from the DHCP offer. | ||
func (d *DHCP6) Resolvers() []net.IP { | ||
return d.Reply.Options.DNS() | ||
} | ||
|
||
// Hostname returns the hostname from the DHCP offer. | ||
func (d *DHCP6) Hostname() (hostname string) { | ||
fqdn := d.Reply.Options.FQDN() | ||
|
||
if fqdn != nil && fqdn.DomainName != nil { | ||
hostname = strings.Join(fqdn.DomainName.Labels, ".") | ||
} else { | ||
hostname = fmt.Sprintf("%s-%s", "talos", strings.ReplaceAll(d.Address().IP.String(), ":", "")) | ||
} | ||
|
||
return hostname | ||
} | ||
|
||
// discover handles the actual DHCP conversation. | ||
func (d *DHCP6) discover(ctx context.Context) error { | ||
if err := waitIPv6LinkReady(d.NetIf); err != nil { | ||
log.Printf("failed waiting for IPv6 readiness: %s", err) | ||
|
||
return err | ||
} | ||
|
||
cli, err := nclient6.New(d.NetIf.Name) | ||
if err != nil { | ||
log.Printf("failed to create dhcp6 client: %s", err) | ||
|
||
return err | ||
} | ||
|
||
// nolint: errcheck | ||
defer cli.Close() | ||
|
||
reply, err := cli.RapidSolicit(ctx) | ||
if err != nil { | ||
// TODO: Make this a well defined error so we can make it not fatal | ||
log.Printf("failed dhcp6 request for %q: %v", d.NetIf.Name, err) | ||
|
||
return err | ||
} | ||
|
||
log.Printf("DHCP6 REPLY on %q: %s", d.NetIf.Name, collapseSummary(reply.Summary())) | ||
|
||
d.Reply = reply | ||
|
||
return nil | ||
} | ||
|
||
func waitIPv6LinkReady(iface *net.Interface) error { | ||
conn, err := rtnetlink.Dial(nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer conn.Close() //nolint: errcheck | ||
|
||
return retry.Constant(30*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(func() error { | ||
ready, err := isIPv6LinkReady(iface, conn) | ||
if err != nil { | ||
return retry.UnexpectedError(err) | ||
} | ||
|
||
if !ready { | ||
return retry.ExpectedError(fmt.Errorf("IPv6 address is still tentative")) | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
|
||
// isIPv6LinkReady returns true if the interface has a link-local address | ||
// which is not tentative. | ||
func isIPv6LinkReady(iface *net.Interface, conn *rtnetlink.Conn) (bool, error) { | ||
addrs, err := conn.Address.List() | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
for _, addr := range addrs { | ||
if addr.Index != uint32(iface.Index) { | ||
continue | ||
} | ||
|
||
if addr.Family != unix.AF_INET6 { | ||
continue | ||
} | ||
|
||
if addr.Attributes.Address.IsLinkLocalUnicast() && (addr.Flags&unix.IFA_F_TENTATIVE == 0) { | ||
if addr.Flags&unix.IFA_F_DADFAILED != 0 { | ||
log.Printf("DADFAILED for %v, continuing anyhow", addr.Attributes.Address) | ||
} | ||
|
||
return true, nil | ||
} | ||
} | ||
|
||
return false, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.