diff --git a/.vscode/settings.json b/.vscode/settings.json
index b0e6df67..ba3550bf 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -9,5 +9,6 @@
"synctrace"
]
},
- "git.ignoreLimitWarning": true
+ "git.ignoreLimitWarning": true,
+ "cmake.sourceDirectory": "/workspaces/kvm-static-ip/internal/native/cgo"
}
\ No newline at end of file
diff --git a/internal/network/types/rpc.go b/internal/network/types/rpc.go
index 748d778e..431c5100 100644
--- a/internal/network/types/rpc.go
+++ b/internal/network/types/rpc.go
@@ -1,20 +1,36 @@
package types
-import "time"
+import (
+ "time"
+ "golang.org/x/sys/unix"
+)
+
+// RpcIPv6Address is the RPC representation of an IPv6 address
type RpcIPv6Address struct {
Address string `json:"address"`
Prefix string `json:"prefix"`
ValidLifetime *time.Time `json:"valid_lifetime"`
PreferredLifetime *time.Time `json:"preferred_lifetime"`
Scope int `json:"scope"`
+ Flags int `json:"flags"`
+ FlagSecondary bool `json:"flag_secondary"`
+ FlagPermanent bool `json:"flag_permanent"`
+ FlagTemporary bool `json:"flag_temporary"`
+ FlagStablePrivacy bool `json:"flag_stable_privacy"`
+ FlagDeprecated bool `json:"flag_deprecated"`
+ FlagOptimistic bool `json:"flag_optimistic"`
+ FlagDADFailed bool `json:"flag_dad_failed"`
+ FlagTentative bool `json:"flag_tentative"`
}
+// RpcInterfaceState is the RPC representation of an interface state
type RpcInterfaceState struct {
InterfaceState
IPv6Addresses []RpcIPv6Address `json:"ipv6_addresses"`
}
+// ToRpcInterfaceState converts an InterfaceState to a RpcInterfaceState
func (s *InterfaceState) ToRpcInterfaceState() *RpcInterfaceState {
addrs := make([]RpcIPv6Address, len(s.IPv6Addresses))
for i, addr := range s.IPv6Addresses {
@@ -24,6 +40,15 @@ func (s *InterfaceState) ToRpcInterfaceState() *RpcInterfaceState {
ValidLifetime: addr.ValidLifetime,
PreferredLifetime: addr.PreferredLifetime,
Scope: addr.Scope,
+ Flags: addr.Flags,
+ FlagSecondary: addr.Flags&unix.IFA_F_SECONDARY != 0,
+ FlagPermanent: addr.Flags&unix.IFA_F_PERMANENT != 0,
+ FlagTemporary: addr.Flags&unix.IFA_F_TEMPORARY != 0,
+ FlagStablePrivacy: addr.Flags&unix.IFA_F_STABLE_PRIVACY != 0,
+ FlagDeprecated: addr.Flags&unix.IFA_F_DEPRECATED != 0,
+ FlagOptimistic: addr.Flags&unix.IFA_F_OPTIMISTIC != 0,
+ FlagDADFailed: addr.Flags&unix.IFA_F_DADFAILED != 0,
+ FlagTentative: addr.Flags&unix.IFA_F_TENTATIVE != 0,
}
}
return &RpcInterfaceState{
diff --git a/internal/network/types/type.go b/internal/network/types/type.go
index 2bee48ac..b770fcd5 100644
--- a/internal/network/types/type.go
+++ b/internal/network/types/type.go
@@ -65,6 +65,7 @@ type IPv6Address struct {
Prefix net.IPNet `json:"prefix"`
ValidLifetime *time.Time `json:"valid_lifetime"`
PreferredLifetime *time.Time `json:"preferred_lifetime"`
+ Flags int `json:"flags"`
Scope int `json:"scope"`
}
diff --git a/pkg/nmlite/interface_state.go b/pkg/nmlite/interface_state.go
index f29c8f22..8a60784b 100644
--- a/pkg/nmlite/interface_state.go
+++ b/pkg/nmlite/interface_state.go
@@ -110,6 +110,7 @@ func (im *InterfaceManager) updateInterfaceStateAddresses(nl *link.Link) (bool,
Address: addr.IP,
Prefix: *addr.IPNet,
Scope: addr.Scope,
+ Flags: addr.Flags,
ValidLifetime: lifetimeToTime(addr.ValidLft),
PreferredLifetime: lifetimeToTime(addr.PreferedLft),
})
diff --git a/pkg/nmlite/jetdhcpc/client.go b/pkg/nmlite/jetdhcpc/client.go
index b3460621..bcab787d 100644
--- a/pkg/nmlite/jetdhcpc/client.go
+++ b/pkg/nmlite/jetdhcpc/client.go
@@ -107,8 +107,9 @@ type Client struct {
}
var (
- defaultTimerDuration = 1 * time.Second
- defaultLinkUpTimeout = 30 * time.Second
+ defaultTimerDuration = 1 * time.Second
+ defaultLinkUpTimeout = 30 * time.Second
+ maxRenewalAttemptDuration = 2 * time.Hour
)
// NewClient creates a new DHCP client for the given interface.
@@ -155,10 +156,21 @@ func resetTimer(t *time.Timer, l *zerolog.Logger) {
t.Reset(defaultTimerDuration)
}
+func getRenewalTime(lease *Lease) time.Duration {
+ if lease.RenewalTime <= 0 || lease.LeaseTime > maxRenewalAttemptDuration/2 {
+ return maxRenewalAttemptDuration
+ }
+
+ return lease.RenewalTime
+}
+
func (c *Client) requestLoop(t *time.Timer, family int, ifname string) {
+ l := c.l.With().Str("interface", ifname).Int("family", family).Logger()
for range t.C {
+ l.Info().Msg("requesting lease")
+
if _, err := c.ensureInterfaceUp(ifname); err != nil {
- c.l.Error().Err(err).Msg("failed to ensure interface up")
+ l.Error().Err(err).Msg("failed to ensure interface up")
resetTimer(t, c.l)
continue
}
@@ -174,12 +186,22 @@ func (c *Client) requestLoop(t *time.Timer, family int, ifname string) {
lease, err = c.requestLease6(ifname)
}
if err != nil {
- c.l.Error().Err(err).Msg("failed to request lease")
+ l.Error().Err(err).Msg("failed to request lease")
resetTimer(t, c.l)
continue
}
c.handleLeaseChange(lease)
+
+ nextRenewal := getRenewalTime(lease)
+
+ l.Info().
+ Dur("nextRenewal", nextRenewal).
+ Dur("leaseTime", lease.LeaseTime).
+ Dur("rebindingTime", lease.RebindingTime).
+ Msg("sleeping until next renewal")
+
+ t.Reset(nextRenewal)
}
}
@@ -262,25 +284,9 @@ func (c *Client) handleLeaseChange(lease *Lease) {
}
}
-func (c *Client) doRenewLoop() {
- timer := time.NewTimer(time.Duration(c.currentLease4.RenewalTime) * time.Second)
- defer timer.Stop()
-
- for range timer.C {
- c.renew()
- }
-}
-
-func (c *Client) renew() {
- // for lease := range c.sendRequests(c.cfg.IPv4, c.cfg.IPv6) {
- // if lease, ok := lease.(*Lease); ok {
- // c.handleLeaseChange(lease)
- // }
- // }
-}
-
func (c *Client) Renew() error {
- go c.renew()
+ c.timer4.Reset(defaultTimerDuration)
+ c.timer6.Reset(defaultTimerDuration)
return nil
}
@@ -304,9 +310,11 @@ func (c *Client) SetIPv4(ipv4 bool) {
c.lease4Mu.Lock()
c.currentLease4 = nil
c.lease4Mu.Unlock()
+
+ c.timer4.Stop()
}
- c.timer4.Stop()
+ c.timer4.Reset(defaultTimerDuration)
}
func (c *Client) SetIPv6(ipv6 bool) {
@@ -323,10 +331,12 @@ func (c *Client) SetIPv6(ipv6 bool) {
if !ipv6 {
c.lease6Mu.Lock()
c.currentLease6 = nil
- c.lease4Mu.Unlock()
+ c.lease6Mu.Unlock()
+
+ c.timer6.Stop()
}
- c.timer6.Stop()
+ c.timer6.Reset(defaultTimerDuration)
}
func (c *Client) Start() error {
diff --git a/pkg/nmlite/jetdhcpc/dhcp4.go b/pkg/nmlite/jetdhcpc/dhcp4.go
index dda0350e..4eb0ee14 100644
--- a/pkg/nmlite/jetdhcpc/dhcp4.go
+++ b/pkg/nmlite/jetdhcpc/dhcp4.go
@@ -47,9 +47,20 @@ func (c *Client) requestLease4(ifname string) (*Lease, error) {
}
l.Info().Msg("attempting to get DHCPv4 lease")
- lease, err := client.Request(c.ctx, reqmods...)
- if err != nil {
- return nil, err
+ var (
+ lease *nclient4.Lease
+ reqErr error
+ )
+ if c.currentLease4 != nil {
+ l.Info().Msg("current lease is not nil, renewing")
+ lease, reqErr = client.Renew(c.ctx, c.currentLease4.p4, reqmods...)
+ } else {
+ l.Info().Msg("current lease is nil, requesting new lease")
+ lease, reqErr = client.Request(c.ctx, reqmods...)
+ }
+
+ if reqErr != nil {
+ return nil, reqErr
}
if lease == nil || lease.ACK == nil {
diff --git a/pkg/nmlite/utils.go b/pkg/nmlite/utils.go
index 15d5624c..5a6eb0b8 100644
--- a/pkg/nmlite/utils.go
+++ b/pkg/nmlite/utils.go
@@ -58,9 +58,15 @@ func compareIPv6AddressSlices(a, b []types.IPv6Address) bool {
if a[i].Address.String() != b[i].Address.String() {
return false
}
+
if a[i].Prefix.String() != b[i].Prefix.String() {
return false
}
+
+ if a[i].Flags != b[i].Flags {
+ return false
+ }
+
// we don't compare the lifetimes because they are not always same
if a[i].Scope != b[i].Scope {
return false
diff --git a/ui/src/components/Ipv6NetworkCard.tsx b/ui/src/components/Ipv6NetworkCard.tsx
index 5ed27ca1..d34da739 100644
--- a/ui/src/components/Ipv6NetworkCard.tsx
+++ b/ui/src/components/Ipv6NetworkCard.tsx
@@ -1,8 +1,21 @@
+import { cx } from "@/cva.config";
import { NetworkState } from "../hooks/stores";
import { LifeTimeLabel } from "../routes/devices.$id.settings.network";
import { GridCard } from "./Card";
+export function FlagLabel({ flag, className }: { flag: string, className?: string }) {
+ const classes = cx(
+ "ml-2 rounded-sm bg-red-500 px-2 py-1 text-[10px] font-medium leading-none text-white dark:border",
+ "bg-red-500 text-white dark:border-red-700 dark:bg-red-800 dark:text-red-50",
+ className,
+ );
+
+ return
+ {flag}
+
+}
+
export default function Ipv6NetworkCard({
networkState,
}: {
@@ -49,7 +62,13 @@ export default function Ipv6NetworkCard({
Address
- {addr.address}
+
+ {addr.address}
+
+ {addr.flag_deprecated ? : null}
+ {addr.flag_dad_failed ? : null}
+
+
{addr.valid_lifetime && (
diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts
index ca8ff46f..1b8dae1b 100644
--- a/ui/src/hooks/stores.ts
+++ b/ui/src/hooks/stores.ts
@@ -699,6 +699,15 @@ export interface IPv6Address {
valid_lifetime: string;
preferred_lifetime: string;
scope: string;
+ flags: number;
+ flag_secondary?: boolean;
+ flag_permanent?: boolean;
+ flag_temporary?: boolean;
+ flag_stable_privacy?: boolean;
+ flag_deprecated?: boolean;
+ flag_optimistic?: boolean;
+ flag_dad_failed?: boolean;
+ flag_tentative?: boolean;
}
export interface NetworkState {