-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add handling for Reserved IPv6 assignment and unassignment
- Loading branch information
1 parent
ba1d425
commit 377c300
Showing
8 changed files
with
252 additions
and
8 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
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,67 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package actioner | ||
|
||
import ( | ||
"sync/atomic" | ||
|
||
"github.com/digitalocean/droplet-agent/internal/log" | ||
"github.com/digitalocean/droplet-agent/internal/metadata" | ||
"github.com/digitalocean/droplet-agent/internal/reservedipv6" | ||
) | ||
|
||
const ( | ||
rip6LogPrefix string = "[Reserved IPv6 Actioner]" | ||
) | ||
|
||
// NewReservedIPv6Actioner returns a new DigitalOcean Reserved IPv6 actioner | ||
func NewReservedIPv6Actioner(mgr reservedipv6.Manager) MetadataActioner { | ||
return &reservedIPv6Actioner{ | ||
mgr: mgr, | ||
allDone: make(chan struct{}, 1), | ||
} | ||
} | ||
|
||
type reservedIPv6Actioner struct { | ||
mgr reservedipv6.Manager | ||
activeActions int32 | ||
closing uint32 | ||
allDone chan struct{} | ||
} | ||
|
||
func (da *reservedIPv6Actioner) Do(md *metadata.Metadata) { | ||
atomic.AddInt32(&da.activeActions, 1) | ||
defer func() { | ||
ret := atomic.AddInt32(&da.activeActions, -1) | ||
if ret == 0 && atomic.LoadUint32(&da.closing) == 1 { | ||
close(da.allDone) | ||
} | ||
}() | ||
|
||
ipv6 := md.ReservedIP.IPv6 | ||
|
||
if ipv6.Active { | ||
log.Info("%s Attempting to assign Reserved IPv6 address '%s'", rip6LogPrefix, ipv6.IPAddress) | ||
if err := da.mgr.Assign(ipv6.IPAddress); err != nil { | ||
log.Error("%s failed to assign Reserved IPv6 address '%s': %v", rip6LogPrefix, ipv6.IPAddress, err) | ||
} | ||
log.Info("%s Assigned Reserved IPv6 address '%s'", rip6LogPrefix, ipv6.IPAddress) | ||
} else { | ||
log.Info("%s Attempting to unassign all Reserved IPv6 addresses", rip6LogPrefix) | ||
if err := da.mgr.Unassign(); err != nil { | ||
log.Error("%s failed to unassign all Reserved IPv6 addresses '%s': %v", rip6LogPrefix, err) | ||
} | ||
log.Info("%s Unassigned all Reserved IPv6 addresses", rip6LogPrefix) | ||
} | ||
} | ||
|
||
func (da *reservedIPv6Actioner) Shutdown() { | ||
log.Info("%s Shutting down", rip6LogPrefix) | ||
atomic.StoreUint32(&da.closing, 1) | ||
if atomic.LoadInt32(&da.activeActions) != 0 { | ||
// if there are still jobs in progress, wait for them to finish | ||
log.Debug("%s Waiting for jobs in progress", rip6LogPrefix) | ||
<-da.allDone | ||
} | ||
log.Info("%s Bye-bye", rip6LogPrefix) | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package reservedipv6 | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"net/netip" | ||
|
||
"github.com/jsimonetti/rtnetlink/v2" | ||
"github.com/mdlayher/netlink" | ||
"golang.org/x/sys/unix" | ||
) | ||
|
||
const ( | ||
logPrefix string = "[Reserved IPv6 Manager]" | ||
|
||
loIface string = "lo" | ||
eth0Iface string = "eth0" | ||
prefixLen uint8 = 124 | ||
) | ||
|
||
type Manager interface { | ||
Assign(ip string) error | ||
Unassign() error | ||
} | ||
|
||
type mgr struct { | ||
nlConn *rtnetlink.Conn | ||
loIdx uint32 | ||
eth0Idx uint32 | ||
} | ||
|
||
func NewManager(netlink *rtnetlink.Conn) (Manager, error) { | ||
lo, err := net.InterfaceByName(loIface) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to determine index for interface '%s': %w", loIface, err) | ||
} | ||
|
||
eth0, err := net.InterfaceByName(eth0Iface) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to determine index for interface '%s': %w", eth0Iface, err) | ||
} | ||
|
||
return &mgr{ | ||
nlConn: netlink, | ||
loIdx: uint32(lo.Index), | ||
eth0Idx: uint32(eth0.Index), | ||
}, nil | ||
} | ||
|
||
// Assign creates a new global-scoped IPv6 address on | ||
func (m *mgr) Assign(ip string) error { | ||
addr, err := netip.ParseAddr(ip) | ||
if err != nil { | ||
return fmt.Errorf("invalid IP: %w", err) | ||
} | ||
if addr.Is4() || addr.Is4In6() { | ||
return fmt.Errorf("IP must be an IPv6 address") | ||
} | ||
|
||
// Equivalent to `ip -6 addr replace "${ip}/124" dev lo scope global` | ||
req := reservedIPv6Addr(m.loIdx, addr) | ||
flags := netlink.Request | netlink.Replace | netlink.Acknowledge | ||
if _, err := m.nlConn.Execute(req, unix.RTM_NEWADDR, flags); err != nil { | ||
return fmt.Errorf("failed to assign address: %w", err) | ||
} | ||
|
||
if err := m.nlConn.Route.Replace(defaultIPv6Route(m.eth0Idx)); err != nil { | ||
return fmt.Errorf("failed to replace default IPv6 route on eth0: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Unassign removes all global-scoped IPv6 addresses on localhost | ||
func (m *mgr) Unassign() error { | ||
addrs, err := m.nlConn.Address.List() | ||
if err != nil { | ||
fmt.Errorf("failed to list addreses: %w", err) | ||
} | ||
|
||
for _, a := range addrs { | ||
if a.Index == m.loIdx && a.Family == unix.AF_INET6 && a.Scope == unix.RT_SCOPE_UNIVERSE { | ||
if err := m.nlConn.Address.Delete(&a); err != nil { | ||
return fmt.Errorf("failed to delete address '%s' from interface '%s': %w", a.Attributes.Address, loIface, err) | ||
} | ||
} | ||
} | ||
|
||
route := defaultIPv6Route(m.eth0Idx) | ||
if _, err := m.nlConn.Route.Get(route); err != nil { | ||
fmt.Errorf("failed to check if default IPv6 route already exists: %w", err) | ||
} | ||
if err := m.nlConn.Route.Delete(defaultIPv6Route(m.eth0Idx)); err != nil { | ||
return fmt.Errorf("failed to remove default IPv6 route on %s: %w", eth0Iface, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func reservedIPv6Addr(intfIdx uint32, addr netip.Addr) *rtnetlink.AddressMessage { | ||
return &rtnetlink.AddressMessage{ | ||
Family: unix.AF_INET6, | ||
PrefixLength: prefixLen, | ||
Scope: unix.RT_SCOPE_UNIVERSE, // global | ||
Index: intfIdx, | ||
Attributes: &rtnetlink.AddressAttributes{ | ||
Address: net.IP(addr.AsSlice()), | ||
}, | ||
} | ||
} | ||
|
||
// defaultIPv6Route returns a route equivalent to `ip -6 route replace default dev eth0` | ||
func defaultIPv6Route(intfIdx uint32) *rtnetlink.RouteMessage { | ||
return &rtnetlink.RouteMessage{ | ||
Family: unix.AF_INET6, | ||
Table: unix.RT_TABLE_MAIN, | ||
Protocol: unix.RTPROT_BOOT, | ||
Type: unix.RTN_UNICAST, | ||
Scope: unix.RT_SCOPE_UNIVERSE, | ||
DstLength: 0, // default route | ||
Attributes: rtnetlink.RouteAttributes{ | ||
Dst: nil, // default route | ||
OutIface: intfIdx, | ||
Priority: 1024, | ||
}, | ||
} | ||
} |
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,3 @@ | ||
package reservedipv6 | ||
|
||
var _ Manager = (*mgr)(nil) |