mirror of https://github.com/jetkvm/kvm.git
Compare commits
No commits in common. "a9cd36c5fbb96c218f50955613fe85db8b8990a8" and "97844a8cafd5f6d3c2b81c2c88c38ce820c15354" have entirely different histories.
a9cd36c5fb
...
97844a8caf
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "JetKVM docker devcontainer",
|
"name": "JetKVM",
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
|
|
@ -32,5 +32,4 @@ wget https://github.com/jetkvm/rv1106-system/releases/download/${BUILDKIT_VERSIO
|
||||||
sudo mkdir -p /opt/jetkvm-native-buildkit && \
|
sudo mkdir -p /opt/jetkvm-native-buildkit && \
|
||||||
sudo tar --use-compress-program="unzstd --long=31" -xvf buildkit.tar.zst -C /opt/jetkvm-native-buildkit && \
|
sudo tar --use-compress-program="unzstd --long=31" -xvf buildkit.tar.zst -C /opt/jetkvm-native-buildkit && \
|
||||||
rm buildkit.tar.zst
|
rm buildkit.tar.zst
|
||||||
popd
|
popd
|
||||||
rm -rf "${BUILDKIT_TMPDIR}"
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "JetKVM podman devcontainer",
|
|
||||||
"image": "mcr.microsoft.com/devcontainers/go:1.25-trixie",
|
|
||||||
"features": {
|
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
|
||||||
// Should match what is defined in ui/package.json
|
|
||||||
"version": "22.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"runArgs": [
|
|
||||||
"--userns=keep-id",
|
|
||||||
"--security-opt=label=disable",
|
|
||||||
"--security-opt=label=nested"
|
|
||||||
],
|
|
||||||
"containerUser": "vscode",
|
|
||||||
"containerEnv": {
|
|
||||||
"HOME": "/home/vscode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -104,7 +104,6 @@ type Config struct {
|
||||||
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
UsbDevices *usbgadget.Devices `json:"usb_devices"`
|
||||||
NetworkConfig *types.NetworkConfig `json:"network_config"`
|
NetworkConfig *types.NetworkConfig `json:"network_config"`
|
||||||
DefaultLogLevel string `json:"default_log_level"`
|
DefaultLogLevel string `json:"default_log_level"`
|
||||||
VideoSleepAfterSec int `json:"video_sleep_after_sec"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) GetDisplayRotation() uint16 {
|
func (c *Config) GetDisplayRotation() uint16 {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ type Native struct {
|
||||||
onVideoFrameReceived func(frame []byte, duration time.Duration)
|
onVideoFrameReceived func(frame []byte, duration time.Duration)
|
||||||
onIndevEvent func(event string)
|
onIndevEvent func(event string)
|
||||||
onRpcEvent func(event string)
|
onRpcEvent func(event string)
|
||||||
sleepModeSupported bool
|
|
||||||
videoLock sync.Mutex
|
videoLock sync.Mutex
|
||||||
screenLock sync.Mutex
|
screenLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
@ -63,8 +62,6 @@ func NewNative(opts NativeOptions) *Native {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sleepModeSupported := isSleepModeSupported()
|
|
||||||
|
|
||||||
return &Native{
|
return &Native{
|
||||||
ready: make(chan struct{}),
|
ready: make(chan struct{}),
|
||||||
l: nativeLogger,
|
l: nativeLogger,
|
||||||
|
|
@ -76,7 +73,6 @@ func NewNative(opts NativeOptions) *Native {
|
||||||
onVideoFrameReceived: onVideoFrameReceived,
|
onVideoFrameReceived: onVideoFrameReceived,
|
||||||
onIndevEvent: onIndevEvent,
|
onIndevEvent: onIndevEvent,
|
||||||
onRpcEvent: onRpcEvent,
|
onRpcEvent: onRpcEvent,
|
||||||
sleepModeSupported: sleepModeSupported,
|
|
||||||
videoLock: sync.Mutex{},
|
videoLock: sync.Mutex{},
|
||||||
screenLock: sync.Mutex{},
|
screenLock: sync.Mutex{},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
package native
|
package native
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const sleepModeFile = "/sys/devices/platform/ff470000.i2c/i2c-4/4-000f/sleep_mode"
|
|
||||||
|
|
||||||
// VideoState is the state of the video stream.
|
|
||||||
type VideoState struct {
|
type VideoState struct {
|
||||||
Ready bool `json:"ready"`
|
Ready bool `json:"ready"`
|
||||||
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
|
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
|
||||||
|
|
@ -15,58 +8,6 @@ type VideoState struct {
|
||||||
FramePerSecond float64 `json:"fps"`
|
FramePerSecond float64 `json:"fps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func isSleepModeSupported() bool {
|
|
||||||
_, err := os.Stat(sleepModeFile)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Native) setSleepMode(enabled bool) error {
|
|
||||||
if !n.sleepModeSupported {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
bEnabled := "0"
|
|
||||||
if enabled {
|
|
||||||
bEnabled = "1"
|
|
||||||
}
|
|
||||||
return os.WriteFile(sleepModeFile, []byte(bEnabled), 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Native) getSleepMode() (bool, error) {
|
|
||||||
if !n.sleepModeSupported {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := os.ReadFile(sleepModeFile)
|
|
||||||
if err == nil {
|
|
||||||
return string(data) == "1", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VideoSetSleepMode sets the sleep mode for the video stream.
|
|
||||||
func (n *Native) VideoSetSleepMode(enabled bool) error {
|
|
||||||
n.videoLock.Lock()
|
|
||||||
defer n.videoLock.Unlock()
|
|
||||||
|
|
||||||
return n.setSleepMode(enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VideoGetSleepMode gets the sleep mode for the video stream.
|
|
||||||
func (n *Native) VideoGetSleepMode() (bool, error) {
|
|
||||||
n.videoLock.Lock()
|
|
||||||
defer n.videoLock.Unlock()
|
|
||||||
|
|
||||||
return n.getSleepMode()
|
|
||||||
}
|
|
||||||
|
|
||||||
// VideoSleepModeSupported checks if the sleep mode is supported.
|
|
||||||
func (n *Native) VideoSleepModeSupported() bool {
|
|
||||||
return n.sleepModeSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
// VideoSetQualityFactor sets the quality factor for the video stream.
|
|
||||||
func (n *Native) VideoSetQualityFactor(factor float64) error {
|
func (n *Native) VideoSetQualityFactor(factor float64) error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -74,7 +15,6 @@ func (n *Native) VideoSetQualityFactor(factor float64) error {
|
||||||
return videoSetStreamQualityFactor(factor)
|
return videoSetStreamQualityFactor(factor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoGetQualityFactor gets the quality factor for the video stream.
|
|
||||||
func (n *Native) VideoGetQualityFactor() (float64, error) {
|
func (n *Native) VideoGetQualityFactor() (float64, error) {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -82,7 +22,6 @@ func (n *Native) VideoGetQualityFactor() (float64, error) {
|
||||||
return videoGetStreamQualityFactor()
|
return videoGetStreamQualityFactor()
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoSetEDID sets the EDID for the video stream.
|
|
||||||
func (n *Native) VideoSetEDID(edid string) error {
|
func (n *Native) VideoSetEDID(edid string) error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -90,7 +29,6 @@ func (n *Native) VideoSetEDID(edid string) error {
|
||||||
return videoSetEDID(edid)
|
return videoSetEDID(edid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoGetEDID gets the EDID for the video stream.
|
|
||||||
func (n *Native) VideoGetEDID() (string, error) {
|
func (n *Native) VideoGetEDID() (string, error) {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -98,7 +36,6 @@ func (n *Native) VideoGetEDID() (string, error) {
|
||||||
return videoGetEDID()
|
return videoGetEDID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoLogStatus gets the log status for the video stream.
|
|
||||||
func (n *Native) VideoLogStatus() (string, error) {
|
func (n *Native) VideoLogStatus() (string, error) {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -106,7 +43,6 @@ func (n *Native) VideoLogStatus() (string, error) {
|
||||||
return videoLogStatus(), nil
|
return videoLogStatus(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoStop stops the video stream.
|
|
||||||
func (n *Native) VideoStop() error {
|
func (n *Native) VideoStop() error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
@ -115,14 +51,10 @@ func (n *Native) VideoStop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoStart starts the video stream.
|
|
||||||
func (n *Native) VideoStart() error {
|
func (n *Native) VideoStart() error {
|
||||||
n.videoLock.Lock()
|
n.videoLock.Lock()
|
||||||
defer n.videoLock.Unlock()
|
defer n.videoLock.Unlock()
|
||||||
|
|
||||||
// disable sleep mode before starting video
|
|
||||||
_ = n.setSleepMode(false)
|
|
||||||
|
|
||||||
videoStart()
|
videoStart()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1215,8 +1215,6 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"getEDID": {Func: rpcGetEDID},
|
"getEDID": {Func: rpcGetEDID},
|
||||||
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
||||||
"getVideoLogStatus": {Func: rpcGetVideoLogStatus},
|
"getVideoLogStatus": {Func: rpcGetVideoLogStatus},
|
||||||
"getVideoSleepMode": {Func: rpcGetVideoSleepMode},
|
|
||||||
"setVideoSleepMode": {Func: rpcSetVideoSleepMode, Params: []string{"duration"}},
|
|
||||||
"getDevChannelState": {Func: rpcGetDevChannelState},
|
"getDevChannelState": {Func: rpcGetDevChannelState},
|
||||||
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
||||||
"getLocalVersion": {Func: rpcGetLocalVersion},
|
"getLocalVersion": {Func: rpcGetLocalVersion},
|
||||||
|
|
|
||||||
3
main.go
3
main.go
|
|
@ -75,9 +75,6 @@ func Main() {
|
||||||
}
|
}
|
||||||
initJiggler()
|
initJiggler()
|
||||||
|
|
||||||
// start video sleep mode timer
|
|
||||||
startVideoSleepModeTicker()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(15 * time.Minute)
|
time.Sleep(15 * time.Minute)
|
||||||
for {
|
for {
|
||||||
|
|
|
||||||
|
|
@ -727,20 +727,6 @@ func (im *InterfaceManager) ReconcileLinkAddrs(addrs []types.IPAddress, family i
|
||||||
|
|
||||||
// applyDHCPLease applies DHCP lease configuration using ReconcileLinkAddrs
|
// applyDHCPLease applies DHCP lease configuration using ReconcileLinkAddrs
|
||||||
func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
|
func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
|
||||||
if lease == nil {
|
|
||||||
return fmt.Errorf("DHCP lease is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if lease.DHCPClient != "jetdhcpc" {
|
|
||||||
im.logger.Warn().Str("dhcp_client", lease.DHCPClient).Msg("ignoring DHCP lease, not implemented yet")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if lease.IsIPv6() {
|
|
||||||
im.logger.Warn().Msg("ignoring IPv6 DHCP lease, not implemented yet")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert DHCP lease to IPv4Config
|
// Convert DHCP lease to IPv4Config
|
||||||
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
|
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,19 @@ package jetdhcpc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/sync"
|
"github.com/jetkvm/kvm/internal/sync"
|
||||||
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
|
||||||
|
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||||
"github.com/jetkvm/kvm/internal/network/types"
|
"github.com/jetkvm/kvm/internal/network/types"
|
||||||
|
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -81,10 +83,8 @@ type Config struct {
|
||||||
UpdateResolvConf func([]string) error
|
UpdateResolvConf func([]string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is a DHCP client.
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
types.DHCPClient
|
types.DHCPClient
|
||||||
|
|
||||||
ifaces []string
|
ifaces []string
|
||||||
cfg Config
|
cfg Config
|
||||||
l *zerolog.Logger
|
l *zerolog.Logger
|
||||||
|
|
@ -101,28 +101,24 @@ type Client struct {
|
||||||
lease4Mu sync.Mutex
|
lease4Mu sync.Mutex
|
||||||
lease6Mu sync.Mutex
|
lease6Mu sync.Mutex
|
||||||
|
|
||||||
timer4 *time.Timer
|
scheduler gocron.Scheduler
|
||||||
timer6 *time.Timer
|
stateDir string
|
||||||
stateDir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
defaultTimerDuration = 1 * time.Second
|
|
||||||
defaultLinkUpTimeout = 30 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewClient creates a new DHCP client for the given interface.
|
// NewClient creates a new DHCP client for the given interface.
|
||||||
func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logger) (*Client, error) {
|
func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logger) (*Client, error) {
|
||||||
timer4 := time.NewTimer(defaultTimerDuration)
|
scheduler, err := gocron.NewScheduler()
|
||||||
timer6 := time.NewTimer(defaultTimerDuration)
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create scheduler: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cfg := *c
|
cfg := *c
|
||||||
if cfg.LinkUpTimeout == 0 {
|
if cfg.LinkUpTimeout == 0 {
|
||||||
cfg.LinkUpTimeout = defaultLinkUpTimeout
|
cfg.LinkUpTimeout = 30 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Timeout == 0 {
|
if cfg.Timeout == 0 {
|
||||||
cfg.Timeout = defaultLinkUpTimeout
|
cfg.Timeout = 30 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Retries == 0 {
|
if cfg.Retries == 0 {
|
||||||
|
|
@ -130,11 +126,12 @@ func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logge
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
ifaces: ifaces,
|
ifaces: ifaces,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
l: l,
|
l: l,
|
||||||
stateDir: "/run/jetkvm-dhcp",
|
scheduler: scheduler,
|
||||||
|
stateDir: "/run/jetkvm-dhcp",
|
||||||
|
|
||||||
currentLease4: nil,
|
currentLease4: nil,
|
||||||
currentLease6: nil,
|
currentLease6: nil,
|
||||||
|
|
@ -144,45 +141,9 @@ func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logge
|
||||||
|
|
||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
cfgMu: sync.Mutex{},
|
cfgMu: sync.Mutex{},
|
||||||
|
|
||||||
timer4: timer4,
|
|
||||||
timer6: timer6,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetTimer(t *time.Timer, l *zerolog.Logger) {
|
|
||||||
l.Debug().Dur("delay", defaultTimerDuration).Msg("will retry later")
|
|
||||||
t.Reset(defaultTimerDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) requestLoop(t *time.Timer, family int, ifname string) {
|
|
||||||
for range t.C {
|
|
||||||
if _, err := c.ensureInterfaceUp(ifname); err != nil {
|
|
||||||
c.l.Error().Err(err).Msg("failed to ensure interface up")
|
|
||||||
resetTimer(t, c.l)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
lease *Lease
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch family {
|
|
||||||
case link.AfInet:
|
|
||||||
lease, err = c.requestLease4(ifname)
|
|
||||||
case link.AfInet6:
|
|
||||||
lease, err = c.requestLease6(ifname)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
c.l.Error().Err(err).Msg("failed to request lease")
|
|
||||||
resetTimer(t, c.l)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c.handleLeaseChange(lease)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ensureInterfaceUp(ifname string) (*link.Link, error) {
|
func (c *Client) ensureInterfaceUp(ifname string) (*link.Link, error) {
|
||||||
nlm := link.GetNetlinkManager()
|
nlm := link.GetNetlinkManager()
|
||||||
iface, err := nlm.GetLinkByName(ifname)
|
iface, err := nlm.GetLinkByName(ifname)
|
||||||
|
|
@ -192,7 +153,76 @@ func (c *Client) ensureInterfaceUp(ifname string) (*link.Link, error) {
|
||||||
return nlm.EnsureInterfaceUpWithTimeout(c.ctx, iface, c.cfg.LinkUpTimeout)
|
return nlm.EnsureInterfaceUpWithTimeout(c.ctx, iface, c.cfg.LinkUpTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lease4 returns the current IPv4 lease
|
func (c *Client) sendInitialRequests() chan any {
|
||||||
|
return c.sendRequests(c.cfg.IPv4, c.cfg.IPv6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendRequestsFamily(
|
||||||
|
family int,
|
||||||
|
wg *sync.WaitGroup,
|
||||||
|
r *chan any,
|
||||||
|
l *zerolog.Logger,
|
||||||
|
iface *link.Link,
|
||||||
|
) {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(iface *link.Link) {
|
||||||
|
defer wg.Done()
|
||||||
|
var (
|
||||||
|
lease *Lease
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch family {
|
||||||
|
case link.AfInet:
|
||||||
|
lease, err = c.requestLease4(iface)
|
||||||
|
case link.AfInet6:
|
||||||
|
lease, err = c.requestLease6(iface)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
l.Error().Err(err).Msg("Could not get lease")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
(*r) <- lease
|
||||||
|
}(iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendRequests(ipv4, ipv6 bool) chan any {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
// Yeah, this is a hack, until we can cancel all leases in progress.
|
||||||
|
r := make(chan any, 3*len(c.ifaces))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, iface := range c.ifaces {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(ifname string) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
l := c.l.With().Str("interface", ifname).Logger()
|
||||||
|
|
||||||
|
iface, err := c.ensureInterfaceUp(ifname)
|
||||||
|
if err != nil {
|
||||||
|
l.Error().Err(err).Msg("Could not bring up interface")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv4 {
|
||||||
|
c.sendRequestsFamily(link.AfInet, &wg, &r, &l, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipv6 {
|
||||||
|
c.sendRequestsFamily(link.AfInet6, &wg, &r, &l, iface)
|
||||||
|
}
|
||||||
|
}(iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(r)
|
||||||
|
}()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Lease4() *types.DHCPLease {
|
func (c *Client) Lease4() *types.DHCPLease {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
defer c.lease4Mu.Unlock()
|
defer c.lease4Mu.Unlock()
|
||||||
|
|
@ -204,7 +234,6 @@ func (c *Client) Lease4() *types.DHCPLease {
|
||||||
return c.currentLease4.ToDHCPLease()
|
return c.currentLease4.ToDHCPLease()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lease6 returns the current IPv6 lease
|
|
||||||
func (c *Client) Lease6() *types.DHCPLease {
|
func (c *Client) Lease6() *types.DHCPLease {
|
||||||
c.lease6Mu.Lock()
|
c.lease6Mu.Lock()
|
||||||
defer c.lease6Mu.Unlock()
|
defer c.lease6Mu.Unlock()
|
||||||
|
|
@ -216,7 +245,6 @@ func (c *Client) Lease6() *types.DHCPLease {
|
||||||
return c.currentLease6.ToDHCPLease()
|
return c.currentLease6.ToDHCPLease()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domain returns the current domain
|
|
||||||
func (c *Client) Domain() string {
|
func (c *Client) Domain() string {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
defer c.lease4Mu.Unlock()
|
defer c.lease4Mu.Unlock()
|
||||||
|
|
@ -235,23 +263,50 @@ func (c *Client) Domain() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleLeaseChange handles lease changes
|
|
||||||
func (c *Client) handleLeaseChange(lease *Lease) {
|
func (c *Client) handleLeaseChange(lease *Lease) {
|
||||||
// do not use defer here, because we need to unlock the mutex before returning
|
// do not use defer here, because we need to unlock the mutex before returning
|
||||||
|
|
||||||
ipv4 := lease.p4 != nil
|
ipv4 := lease.p4 != nil
|
||||||
|
version := "ipv4"
|
||||||
|
|
||||||
if ipv4 {
|
if ipv4 {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
c.currentLease4 = lease
|
c.currentLease4 = lease
|
||||||
c.lease4Mu.Unlock()
|
|
||||||
} else {
|
} else {
|
||||||
|
version = "ipv6"
|
||||||
c.lease6Mu.Lock()
|
c.lease6Mu.Lock()
|
||||||
c.currentLease6 = lease
|
c.currentLease6 = lease
|
||||||
c.lease6Mu.Unlock()
|
}
|
||||||
|
|
||||||
|
// clear all current jobs with the same tags
|
||||||
|
// c.scheduler.RemoveByTags(version)
|
||||||
|
|
||||||
|
// add scheduler job to renew the lease
|
||||||
|
if lease.RenewalTime > 0 {
|
||||||
|
c.scheduler.NewJob(
|
||||||
|
gocron.DurationJob(time.Duration(lease.RenewalTime)*time.Second),
|
||||||
|
gocron.NewTask(func() {
|
||||||
|
c.l.Info().Msg("renewing lease")
|
||||||
|
for lease := range c.sendRequests(ipv4, !ipv4) {
|
||||||
|
if lease, ok := lease.(*Lease); ok {
|
||||||
|
c.handleLeaseChange(lease)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
gocron.WithName(fmt.Sprintf("renew-%s", version)),
|
||||||
|
gocron.WithSingletonMode(gocron.LimitModeWait),
|
||||||
|
gocron.WithTags(version),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.apply()
|
c.apply()
|
||||||
|
|
||||||
|
if ipv4 {
|
||||||
|
c.lease4Mu.Unlock()
|
||||||
|
} else {
|
||||||
|
c.lease6Mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: handle lease expiration
|
// TODO: handle lease expiration
|
||||||
if c.cfg.OnLease4Change != nil && ipv4 {
|
if c.cfg.OnLease4Change != nil && ipv4 {
|
||||||
c.cfg.OnLease4Change(lease.ToDHCPLease())
|
c.cfg.OnLease4Change(lease.ToDHCPLease())
|
||||||
|
|
@ -262,21 +317,12 @@ 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() {
|
func (c *Client) renew() {
|
||||||
// for lease := range c.sendRequests(c.cfg.IPv4, c.cfg.IPv6) {
|
for lease := range c.sendRequests(c.cfg.IPv4, c.cfg.IPv6) {
|
||||||
// if lease, ok := lease.(*Lease); ok {
|
if lease, ok := lease.(*Lease); ok {
|
||||||
// c.handleLeaseChange(lease)
|
c.handleLeaseChange(lease)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Renew() error {
|
func (c *Client) Renew() error {
|
||||||
|
|
@ -304,29 +350,17 @@ func (c *Client) SetIPv4(ipv4 bool) {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
c.currentLease4 = nil
|
c.currentLease4 = nil
|
||||||
c.lease4Mu.Unlock()
|
c.lease4Mu.Unlock()
|
||||||
|
c.scheduler.RemoveByTags("ipv4")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.timer4.Stop()
|
c.sendRequests(ipv4, c.cfg.IPv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetIPv6(ipv6 bool) {
|
func (c *Client) SetIPv6(ipv6 bool) {
|
||||||
c.cfgMu.Lock()
|
c.cfgMu.Lock()
|
||||||
defer c.cfgMu.Unlock()
|
defer c.cfgMu.Unlock()
|
||||||
|
|
||||||
currentIPv6 := c.cfg.IPv6
|
|
||||||
c.cfg.IPv6 = ipv6
|
c.cfg.IPv6 = ipv6
|
||||||
|
|
||||||
if currentIPv6 == ipv6 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ipv6 {
|
|
||||||
c.lease6Mu.Lock()
|
|
||||||
c.currentLease6 = nil
|
|
||||||
c.lease4Mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.timer6.Stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Start() error {
|
func (c *Client) Start() error {
|
||||||
|
|
@ -334,14 +368,15 @@ func (c *Client) Start() error {
|
||||||
c.l.Warn().Err(err).Msg("failed to kill udhcpc processes, continuing anyway")
|
c.l.Warn().Err(err).Msg("failed to kill udhcpc processes, continuing anyway")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, iface := range c.ifaces {
|
c.scheduler.Start()
|
||||||
if c.cfg.IPv4 {
|
|
||||||
go c.requestLoop(c.timer4, link.AfInet, iface)
|
go func() {
|
||||||
|
for lease := range c.sendInitialRequests() {
|
||||||
|
if lease, ok := lease.(*Lease); ok {
|
||||||
|
c.handleLeaseChange(lease)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if c.cfg.IPv6 {
|
}()
|
||||||
go c.requestLoop(c.timer6, link.AfInet6, iface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,8 @@ import (
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) requestLease4(ifname string) (*Lease, error) {
|
func (c *Client) requestLease4(iface netlink.Link) (*Lease, error) {
|
||||||
iface, err := netlink.LinkByName(ifname)
|
ifname := iface.Attrs().Name
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
l := c.l.With().Str("interface", ifname).Logger()
|
l := c.l.With().Str("interface", ifname).Logger()
|
||||||
|
|
||||||
mods := []nclient4.ClientOpt{
|
mods := []nclient4.ClientOpt{
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,10 @@ func isIPv6RouteReady(serverAddr net.IP) waitForCondition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestLease6(ifname string) (*Lease, error) {
|
func (c *Client) requestLease6(iface netlink.Link) (*Lease, error) {
|
||||||
|
ifname := iface.Attrs().Name
|
||||||
l := c.l.With().Str("interface", ifname).Logger()
|
l := c.l.With().Str("interface", ifname).Logger()
|
||||||
|
|
||||||
iface, err := netlink.LinkByName(ifname)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
clientPort := dhcpv6.DefaultClientPort
|
clientPort := dhcpv6.DefaultClientPort
|
||||||
if c.cfg.V6ClientPort != nil {
|
if c.cfg.V6ClientPort != nil {
|
||||||
clientPort = *c.cfg.V6ClientPort
|
clientPort = *c.cfg.V6ClientPort
|
||||||
|
|
|
||||||
|
|
@ -374,8 +374,8 @@ function UrlView({
|
||||||
icon: FedoraIcon,
|
icon: FedoraIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "openSUSE Leap 16.0",
|
name: "openSUSE Leap 15.6",
|
||||||
url: "https://download.opensuse.org/distribution/leap/16.0/offline/Leap-16.0-online-installer-x86_64.install.iso",
|
url: "https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso",
|
||||||
icon: OpenSUSEIcon,
|
icon: OpenSUSEIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
103
video.go
103
video.go
|
|
@ -1,22 +1,10 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/native"
|
"github.com/jetkvm/kvm/internal/native"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var lastVideoState native.VideoState
|
||||||
lastVideoState native.VideoState
|
|
||||||
videoSleepModeCtx context.Context
|
|
||||||
videoSleepModeCancel context.CancelFunc
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultVideoSleepModeDuration = 1 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
func triggerVideoStateUpdate() {
|
func triggerVideoStateUpdate() {
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -29,92 +17,3 @@ func triggerVideoStateUpdate() {
|
||||||
func rpcGetVideoState() (native.VideoState, error) {
|
func rpcGetVideoState() (native.VideoState, error) {
|
||||||
return lastVideoState, nil
|
return lastVideoState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcVideoSleepModeResponse struct {
|
|
||||||
Supported bool `json:"supported"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func rpcGetVideoSleepMode() rpcVideoSleepModeResponse {
|
|
||||||
sleepMode, _ := nativeInstance.VideoGetSleepMode()
|
|
||||||
return rpcVideoSleepModeResponse{
|
|
||||||
Supported: nativeInstance.VideoSleepModeSupported(),
|
|
||||||
Enabled: sleepMode,
|
|
||||||
Duration: config.VideoSleepAfterSec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rpcSetVideoSleepMode(duration int) error {
|
|
||||||
if duration < 0 {
|
|
||||||
duration = -1 // disable
|
|
||||||
}
|
|
||||||
|
|
||||||
config.VideoSleepAfterSec = duration
|
|
||||||
if err := SaveConfig(); err != nil {
|
|
||||||
return fmt.Errorf("failed to save config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we won't restart the ticker here,
|
|
||||||
// as the session can't be inactive when this function is called
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stopVideoSleepModeTicker() {
|
|
||||||
nativeLogger.Trace().Msg("stopping HDMI sleep mode ticker")
|
|
||||||
|
|
||||||
if videoSleepModeCancel != nil {
|
|
||||||
nativeLogger.Trace().Msg("canceling HDMI sleep mode ticker context")
|
|
||||||
videoSleepModeCancel()
|
|
||||||
videoSleepModeCancel = nil
|
|
||||||
videoSleepModeCtx = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startVideoSleepModeTicker() {
|
|
||||||
if !nativeInstance.VideoSleepModeSupported() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var duration time.Duration
|
|
||||||
|
|
||||||
if config.VideoSleepAfterSec == 0 {
|
|
||||||
duration = defaultVideoSleepModeDuration
|
|
||||||
} else if config.VideoSleepAfterSec > 0 {
|
|
||||||
duration = time.Duration(config.VideoSleepAfterSec) * time.Second
|
|
||||||
} else {
|
|
||||||
stopVideoSleepModeTicker()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop any existing timer and goroutine
|
|
||||||
stopVideoSleepModeTicker()
|
|
||||||
|
|
||||||
// Create new context for this ticker
|
|
||||||
videoSleepModeCtx, videoSleepModeCancel = context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
go doVideoSleepModeTicker(videoSleepModeCtx, duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doVideoSleepModeTicker(ctx context.Context, duration time.Duration) {
|
|
||||||
timer := time.NewTimer(duration)
|
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
nativeLogger.Trace().Msg("HDMI sleep mode ticker started")
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
if getActiveSessions() > 0 {
|
|
||||||
nativeLogger.Warn().Msg("not going to enter HDMI sleep mode because there are active sessions")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeLogger.Trace().Msg("entering HDMI sleep mode")
|
|
||||||
_ = nativeInstance.VideoSetSleepMode(true)
|
|
||||||
case <-ctx.Done():
|
|
||||||
nativeLogger.Trace().Msg("HDMI sleep mode ticker stopped")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
38
webrtc.go
38
webrtc.go
|
|
@ -39,34 +39,6 @@ type Session struct {
|
||||||
keysDownStateQueue chan usbgadget.KeysDownState
|
keysDownStateQueue chan usbgadget.KeysDownState
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
actionSessions int = 0
|
|
||||||
activeSessionsMutex = &sync.Mutex{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func incrActiveSessions() int {
|
|
||||||
activeSessionsMutex.Lock()
|
|
||||||
defer activeSessionsMutex.Unlock()
|
|
||||||
|
|
||||||
actionSessions++
|
|
||||||
return actionSessions
|
|
||||||
}
|
|
||||||
|
|
||||||
func decrActiveSessions() int {
|
|
||||||
activeSessionsMutex.Lock()
|
|
||||||
defer activeSessionsMutex.Unlock()
|
|
||||||
|
|
||||||
actionSessions--
|
|
||||||
return actionSessions
|
|
||||||
}
|
|
||||||
|
|
||||||
func getActiveSessions() int {
|
|
||||||
activeSessionsMutex.Lock()
|
|
||||||
defer activeSessionsMutex.Unlock()
|
|
||||||
|
|
||||||
return actionSessions
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) resetKeepAliveTime() {
|
func (s *Session) resetKeepAliveTime() {
|
||||||
s.keepAliveJitterLock.Lock()
|
s.keepAliveJitterLock.Lock()
|
||||||
defer s.keepAliveJitterLock.Unlock()
|
defer s.keepAliveJitterLock.Unlock()
|
||||||
|
|
@ -340,8 +312,9 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
if connectionState == webrtc.ICEConnectionStateConnected {
|
if connectionState == webrtc.ICEConnectionStateConnected {
|
||||||
if !isConnected {
|
if !isConnected {
|
||||||
isConnected = true
|
isConnected = true
|
||||||
|
actionSessions++
|
||||||
onActiveSessionsChanged()
|
onActiveSessionsChanged()
|
||||||
if incrActiveSessions() == 1 {
|
if actionSessions == 1 {
|
||||||
onFirstSessionConnected()
|
onFirstSessionConnected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -380,8 +353,9 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
}
|
}
|
||||||
if isConnected {
|
if isConnected {
|
||||||
isConnected = false
|
isConnected = false
|
||||||
|
actionSessions--
|
||||||
onActiveSessionsChanged()
|
onActiveSessionsChanged()
|
||||||
if decrActiveSessions() == 0 {
|
if actionSessions == 0 {
|
||||||
onLastSessionDisconnected()
|
onLastSessionDisconnected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -390,16 +364,16 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var actionSessions = 0
|
||||||
|
|
||||||
func onActiveSessionsChanged() {
|
func onActiveSessionsChanged() {
|
||||||
requestDisplayUpdate(true, "active_sessions_changed")
|
requestDisplayUpdate(true, "active_sessions_changed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func onFirstSessionConnected() {
|
func onFirstSessionConnected() {
|
||||||
_ = nativeInstance.VideoStart()
|
_ = nativeInstance.VideoStart()
|
||||||
stopVideoSleepModeTicker()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func onLastSessionDisconnected() {
|
func onLastSessionDisconnected() {
|
||||||
_ = nativeInstance.VideoStop()
|
_ = nativeInstance.VideoStop()
|
||||||
startVideoSleepModeTicker()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue