Compare commits

...

10 Commits

Author SHA1 Message Date
Marc Brooks 6b98fa1d95
Merge 926fcf202a into bde0a086ab 2025-07-03 12:27:26 -05:00
Siyuan Miao bde0a086ab chore: bump to 0.4.7 2025-07-03 19:03:46 +02:00
Aveline 9c9335da31
chore: typo 'supression' should be 'suppression' (#671) 2025-07-03 17:28:00 +02:00
dependabot[bot] 090e0b4b47
build(deps): bump actions/setup-go from 4.2.1 to 5.5.0 (#666)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4.2.1 to 5.5.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4.2.1...v5.5.0)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 5.5.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-03 17:21:11 +02:00
dependabot[bot] 48a7a638a3
build(deps): bump github.com/pion/webrtc/v4 from 4.1.2 to 4.1.3 (#667)
Bumps [github.com/pion/webrtc/v4](https://github.com/pion/webrtc) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/pion/webrtc/releases)
- [Changelog](https://github.com/pion/webrtc/blob/master/.goreleaser.yml)
- [Commits](https://github.com/pion/webrtc/compare/v4.1.2...v4.1.3)

---
updated-dependencies:
- dependency-name: github.com/pion/webrtc/v4
  dependency-version: 4.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-03 17:21:04 +02:00
dependabot[bot] e4f6a713a5
build(deps): bump github.com/Masterminds/semver/v3 from 3.3.1 to 3.4.0 (#668)
Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/Masterminds/semver/releases)
- [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Masterminds/semver/compare/v3.3.1...v3.4.0)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/semver/v3
  dependency-version: 3.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-03 17:19:27 +02:00
Aveline 9fcf74b398
fix(display): reset display state after native binary is restarted (#654)
* fix(usbgadget): add lock for logWithSupression

* fix(display): reset display state after native binary is restarted
2025-07-03 17:18:09 +02:00
Marc Brooks 926fcf202a
Add support for using DHCP-provided NTP server 2025-06-18 21:09:45 -05:00
Marc Brooks 087487fe9c
Add custom NTP and HTTP time sync servers
Since the ordering may have been previously defaulted and saved as "ntp,http", but that was being ignored and fallback-defaults were being used, in Ordering, `ntp` means use the fallback NTP servers, and `http` means use the fallback HTTP URLs. Thus `ntp_user_provided` and `http_user_provided` are the user specified static lists.
2025-06-18 20:27:02 -05:00
Marc Brooks 466bf40658
Ensure the mDNS mode is set every time network state changes
Eliminates (mostly) duplicate code
2025-06-18 12:38:27 -05:00
20 changed files with 215 additions and 65 deletions

View File

@ -23,7 +23,7 @@ jobs:
cache: "npm" cache: "npm"
cache-dependency-path: "**/package-lock.json" cache-dependency-path: "**/package-lock.json"
- name: Set up Golang - name: Set up Golang
uses: actions/setup-go@v5 uses: actions/setup-go@v5.5.0
with: with:
go-version: "1.24.4" go-version: "1.24.4"
- name: Build frontend - name: Build frontend

View File

@ -24,7 +24,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Go - name: Install Go
uses: actions/setup-go@19bb51245e9c80abacb2e91cc42b33fa478b8639 # v4.2.1 uses: actions/setup-go@fa96338abe5531f6e34c5cc0bbe28c1a533d5505 # v4.2.1
with: with:
go-version: 1.24.4 go-version: 1.24.4
- name: Create empty resource directory - name: Create empty resource directory

View File

@ -104,7 +104,7 @@ jobs:
EOF EOF
ssh jkci "cat /tmp/device-tests.json" > device-tests.json ssh jkci "cat /tmp/device-tests.json" > device-tests.json
- name: Set up Golang - name: Set up Golang
uses: actions/setup-go@v5 uses: actions/setup-go@v5.5.0
with: with:
go-version: "1.24.4" go-version: "1.24.4"
- name: Golang Test Report - name: Golang Test Report

View File

@ -2,8 +2,8 @@ BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
BUILDDATE ?= $(shell date -u +%FT%T%z) BUILDDATE ?= $(shell date -u +%FT%T%z)
BUILDTS ?= $(shell date -u +%s) BUILDTS ?= $(shell date -u +%s)
REVISION ?= $(shell git rev-parse HEAD) REVISION ?= $(shell git rev-parse HEAD)
VERSION_DEV ?= 0.4.6-dev$(shell date +%Y%m%d%H%M) VERSION_DEV ?= 0.4.7-dev$(shell date +%Y%m%d%H%M)
VERSION ?= 0.4.5 VERSION ?= 0.4.6
PROMETHEUS_TAG := github.com/prometheus/common/version PROMETHEUS_TAG := github.com/prometheus/common/version
KVM_PKG_NAME := github.com/jetkvm/kvm KVM_PKG_NAME := github.com/jetkvm/kvm

View File

@ -9,9 +9,14 @@ import (
"time" "time"
) )
var currentScreen = "ui_Boot_Screen"
var backlightState = 0 // 0 - NORMAL, 1 - DIMMED, 2 - OFF var backlightState = 0 // 0 - NORMAL, 1 - DIMMED, 2 - OFF
var (
currentScreen = "ui_Boot_Screen"
displayedTexts = make(map[string]string)
screenStateLock = sync.Mutex{}
)
var ( var (
dimTicker *time.Ticker dimTicker *time.Ticker
offTicker *time.Ticker offTicker *time.Ticker
@ -22,6 +27,8 @@ const (
backlightControlClass string = "/sys/class/backlight/backlight/brightness" backlightControlClass string = "/sys/class/backlight/backlight/brightness"
) )
// do not call this function directly, use switchToScreenIfDifferent instead
// this function is not thread safe
func switchToScreen(screen string) { func switchToScreen(screen string) {
_, err := CallCtrlAction("lv_scr_load", map[string]interface{}{"obj": screen}) _, err := CallCtrlAction("lv_scr_load", map[string]interface{}{"obj": screen})
if err != nil { if err != nil {
@ -31,8 +38,6 @@ func switchToScreen(screen string) {
currentScreen = screen currentScreen = screen
} }
var displayedTexts = make(map[string]string)
func lvObjSetState(objName string, state string) (*CtrlResponse, error) { func lvObjSetState(objName string, state string) (*CtrlResponse, error) {
return CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": objName, "state": state}) return CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": objName, "state": state})
} }
@ -78,6 +83,9 @@ func lvDispSetRotation(rotation string) (*CtrlResponse, error) {
} }
func updateLabelIfChanged(objName string, newText string) { func updateLabelIfChanged(objName string, newText string) {
screenStateLock.Lock()
defer screenStateLock.Unlock()
if newText != "" && newText != displayedTexts[objName] { if newText != "" && newText != displayedTexts[objName] {
_, _ = lvLabelSetText(objName, newText) _, _ = lvLabelSetText(objName, newText)
displayedTexts[objName] = newText displayedTexts[objName] = newText
@ -85,12 +93,23 @@ func updateLabelIfChanged(objName string, newText string) {
} }
func switchToScreenIfDifferent(screenName string) { func switchToScreenIfDifferent(screenName string) {
screenStateLock.Lock()
defer screenStateLock.Unlock()
if currentScreen != screenName { if currentScreen != screenName {
displayLogger.Info().Str("from", currentScreen).Str("to", screenName).Msg("switching screen") displayLogger.Info().Str("from", currentScreen).Str("to", screenName).Msg("switching screen")
switchToScreen(screenName) switchToScreen(screenName)
} }
} }
func clearDisplayState() {
screenStateLock.Lock()
defer screenStateLock.Unlock()
displayedTexts = make(map[string]string)
currentScreen = "ui_Boot_Screen"
}
var ( var (
cloudBlinkLock sync.Mutex = sync.Mutex{} cloudBlinkLock sync.Mutex = sync.Mutex{}
cloudBlinkStopped bool cloudBlinkStopped bool

6
go.mod
View File

@ -3,7 +3,7 @@ module github.com/jetkvm/kvm
go 1.24.4 go 1.24.4
require ( require (
github.com/Masterminds/semver/v3 v3.3.1 github.com/Masterminds/semver/v3 v3.4.0
github.com/beevik/ntp v1.4.3 github.com/beevik/ntp v1.4.3
github.com/coder/websocket v1.8.13 github.com/coder/websocket v1.8.13
github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-oidc/v3 v3.14.1
@ -17,7 +17,7 @@ require (
github.com/hanwen/go-fuse/v2 v2.8.0 github.com/hanwen/go-fuse/v2 v2.8.0
github.com/pion/logging v0.2.4 github.com/pion/logging v0.2.4
github.com/pion/mdns/v2 v2.0.7 github.com/pion/mdns/v2 v2.0.7
github.com/pion/webrtc/v4 v4.1.2 github.com/pion/webrtc/v4 v4.1.3
github.com/pojntfx/go-nbd v0.3.2 github.com/pojntfx/go-nbd v0.3.2
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.22.0
github.com/prometheus/common v0.65.0 github.com/prometheus/common v0.65.0
@ -66,7 +66,7 @@ require (
github.com/pion/interceptor v0.1.40 // indirect github.com/pion/interceptor v0.1.40 // indirect
github.com/pion/randutil v0.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.19 // indirect github.com/pion/rtp v1.8.20 // indirect
github.com/pion/sctp v1.8.39 // indirect github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.14 // indirect github.com/pion/sdp/v3 v3.0.14 // indirect
github.com/pion/srtp/v3 v3.0.6 // indirect github.com/pion/srtp/v3 v3.0.6 // indirect

12
go.sum
View File

@ -1,5 +1,5 @@
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho= github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q= github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -117,8 +117,8 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c= github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI=
github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk= github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE= github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI= github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
@ -131,8 +131,8 @@ github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps= github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs= github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54= github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c=
github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U= github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

View File

@ -43,9 +43,11 @@ type testNetworkConfig struct {
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"` LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"` MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"` TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
TimeSyncOrdering []string `json:"time_sync_ordering,omitempty" one_of:"http,ntp,ntp_dhcp,ntp_user_provided,ntp_fallback" default:"ntp,http"` TimeSyncOrdering []string `json:"time_sync_ordering,omitempty" one_of:"http,ntp,ntp_dhcp,ntp_user_provided,http_user_provided" default:"ntp,http"`
TimeSyncDisableFallback null.Bool `json:"time_sync_disable_fallback,omitempty" default:"false"` TimeSyncDisableFallback null.Bool `json:"time_sync_disable_fallback,omitempty" default:"false"`
TimeSyncParallel null.Int `json:"time_sync_parallel,omitempty" default:"4"` TimeSyncParallel null.Int `json:"time_sync_parallel,omitempty" default:"4"`
TimeSyncNTPServers []string `json:"time_sync_ntp_servers,omitempty" validate_type:"ipv4_or_ipv6" required_if:"TimeSyncOrdering=ntp_user_provided"`
TimeSyncHTTPUrls []string `json:"time_sync_http_urls,omitempty" validate_type:"url" required_if:"TimeSyncOrdering=http_user_provided"`
} }
func TestValidateConfig(t *testing.T) { func TestValidateConfig(t *testing.T) {

View File

@ -45,9 +45,11 @@ type NetworkConfig struct {
LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"` LLDPTxTLVs []string `json:"lldp_tx_tlvs,omitempty" one_of:"chassis,port,system,vlan" default:"chassis,port,system,vlan"`
MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"` MDNSMode null.String `json:"mdns_mode,omitempty" one_of:"disabled,auto,ipv4_only,ipv6_only" default:"auto"`
TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"` TimeSyncMode null.String `json:"time_sync_mode,omitempty" one_of:"ntp_only,ntp_and_http,http_only,custom" default:"ntp_and_http"`
TimeSyncOrdering []string `json:"time_sync_ordering,omitempty" one_of:"http,ntp,ntp_dhcp,ntp_user_provided,ntp_fallback" default:"ntp,http"` TimeSyncOrdering []string `json:"time_sync_ordering,omitempty" one_of:"http,ntp,ntp_dhcp,ntp_user_provided,http_user_provided" default:"ntp,http"`
TimeSyncDisableFallback null.Bool `json:"time_sync_disable_fallback,omitempty" default:"false"` TimeSyncDisableFallback null.Bool `json:"time_sync_disable_fallback,omitempty" default:"false"`
TimeSyncParallel null.Int `json:"time_sync_parallel,omitempty" default:"4"` TimeSyncParallel null.Int `json:"time_sync_parallel,omitempty" default:"4"`
TimeSyncNTPServers []string `json:"time_sync_ntp_servers,omitempty" validate_type:"ipv4_or_ipv6" required_if:"TimeSyncOrdering=ntp_user_provided"`
TimeSyncHTTPUrls []string `json:"time_sync_http_urls,omitempty" validate_type:"url" required_if:"TimeSyncOrdering=http_user_provided"`
} }
func (c *NetworkConfig) GetMDNSMode() *mdns.MDNSListenOptions { func (c *NetworkConfig) GetMDNSMode() *mdns.MDNSListenOptions {

View File

@ -21,6 +21,7 @@ type NetworkInterfaceState struct {
ipv6Addr *net.IP ipv6Addr *net.IP
ipv6Addresses []IPv6Address ipv6Addresses []IPv6Address
ipv6LinkLocal *net.IP ipv6LinkLocal *net.IP
ntpAddresses []*net.IP
macAddr *net.HardwareAddr macAddr *net.HardwareAddr
l *zerolog.Logger l *zerolog.Logger
@ -76,6 +77,7 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS
onInitialCheck: opts.OnInitialCheck, onInitialCheck: opts.OnInitialCheck,
cbConfigChange: opts.OnConfigChange, cbConfigChange: opts.OnConfigChange,
config: opts.NetworkConfig, config: opts.NetworkConfig,
ntpAddresses: make([]*net.IP, 0),
} }
// create the dhcp client // create the dhcp client
@ -89,7 +91,7 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS
opts.Logger.Error().Err(err).Msg("failed to update network state") opts.Logger.Error().Err(err).Msg("failed to update network state")
return return
} }
_ = s.updateNtpServersFromLease(lease)
_ = s.setHostnameIfNotSame() _ = s.setHostnameIfNotSame()
opts.OnDhcpLeaseChange(lease) opts.OnDhcpLeaseChange(lease)
@ -135,6 +137,27 @@ func (s *NetworkInterfaceState) IPv6String() string {
return s.ipv6Addr.String() return s.ipv6Addr.String()
} }
func (s *NetworkInterfaceState) NtpAddresses() []*net.IP {
return s.ntpAddresses
}
func (s *NetworkInterfaceState) NtpAddressesString() []string {
ntpServers := []string{}
if s != nil {
s.l.Debug().Any("s", s).Msg("getting NTP address strings")
if len(s.ntpAddresses) > 0 {
for _, server := range s.ntpAddresses {
s.l.Debug().IPAddr("server", *server).Msg("converting NTP address")
ntpServers = append(ntpServers, server.String())
}
}
}
return ntpServers
}
func (s *NetworkInterfaceState) MAC() *net.HardwareAddr { func (s *NetworkInterfaceState) MAC() *net.HardwareAddr {
return s.macAddr return s.macAddr
} }
@ -318,6 +341,25 @@ func (s *NetworkInterfaceState) update() (DhcpTargetState, error) {
return dhcpTargetState, nil return dhcpTargetState, nil
} }
func (s *NetworkInterfaceState) updateNtpServersFromLease(lease *udhcpc.Lease) error {
if lease != nil && len(lease.NTPServers) > 0 {
s.l.Info().Msg("lease found, updating DHCP NTP addresses")
s.ntpAddresses = make([]*net.IP, 0, len(lease.NTPServers))
for _, ntpServer := range lease.NTPServers {
if ntpServer != nil {
s.l.Info().IPAddr("ntp_server", ntpServer).Msg("NTP server found in lease")
s.ntpAddresses = append(s.ntpAddresses, &ntpServer)
}
}
} else {
s.l.Info().Msg("no NTP servers found in lease")
s.ntpAddresses = make([]*net.IP, 0, len(s.config.TimeSyncNTPServers))
}
return nil
}
func (s *NetworkInterfaceState) CheckAndUpdateDhcp() error { func (s *NetworkInterfaceState) CheckAndUpdateDhcp() error {
dhcpTargetState, err := s.update() dhcpTargetState, err := s.update()
if err != nil { if err != nil {

View File

@ -19,9 +19,9 @@ var defaultHTTPUrls = []string{
// "http://www.msftconnecttest.com/connecttest.txt", // "http://www.msftconnecttest.com/connecttest.txt",
} }
func (t *TimeSync) queryAllHttpTime() (now *time.Time) { func (t *TimeSync) queryAllHttpTime(httpUrls []string) (now *time.Time) {
chunkSize := 4 chunkSize := int(t.networkConfig.TimeSyncParallel.ValueOr(4))
httpUrls := t.httpUrls t.l.Info().Strs("httpUrls", httpUrls).Int("chunkSize", chunkSize).Msg("querying HTTP URLs")
// shuffle the http urls to avoid always querying the same servers // shuffle the http urls to avoid always querying the same servers
rand.Shuffle(len(httpUrls), func(i, j int) { httpUrls[i], httpUrls[j] = httpUrls[j], httpUrls[i] }) rand.Shuffle(len(httpUrls), func(i, j int) { httpUrls[i], httpUrls[j] = httpUrls[j], httpUrls[i] })

View File

@ -73,6 +73,7 @@ var (
}, },
[]string{"url"}, []string{"url"},
) )
metricNtpServerInfo = promauto.NewGaugeVec( metricNtpServerInfo = promauto.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "jetkvm_timesync_ntp_server_info", Name: "jetkvm_timesync_ntp_server_info",

View File

@ -1,6 +1,7 @@
package timesync package timesync
import ( import (
"context"
"math/rand/v2" "math/rand/v2"
"strconv" "strconv"
"time" "time"
@ -21,9 +22,9 @@ var defaultNTPServers = []string{
"3.pool.ntp.org", "3.pool.ntp.org",
} }
func (t *TimeSync) queryNetworkTime() (now *time.Time, offset *time.Duration) { func (t *TimeSync) queryNetworkTime(ntpServers []string) (now *time.Time, offset *time.Duration) {
chunkSize := 4 chunkSize := int(t.networkConfig.TimeSyncParallel.ValueOr(4))
ntpServers := t.ntpServers t.l.Info().Strs("servers", ntpServers).Int("chunkSize", chunkSize).Msg("querying NTP servers")
// shuffle the ntp servers to avoid always querying the same servers // shuffle the ntp servers to avoid always querying the same servers
rand.Shuffle(len(ntpServers), func(i, j int) { ntpServers[i], ntpServers[j] = ntpServers[j], ntpServers[i] }) rand.Shuffle(len(ntpServers), func(i, j int) { ntpServers[i], ntpServers[j] = ntpServers[j], ntpServers[i] })
@ -46,6 +47,10 @@ type ntpResult struct {
func (t *TimeSync) queryMultipleNTP(servers []string, timeout time.Duration) (now *time.Time, offset *time.Duration) { func (t *TimeSync) queryMultipleNTP(servers []string, timeout time.Duration) (now *time.Time, offset *time.Duration) {
results := make(chan *ntpResult, len(servers)) results := make(chan *ntpResult, len(servers))
_, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
for _, server := range servers { for _, server := range servers {
go func(server string) { go func(server string) {
scopedLogger := t.l.With(). scopedLogger := t.l.With().
@ -66,15 +71,25 @@ func (t *TimeSync) queryMultipleNTP(servers []string, timeout time.Duration) (no
return return
} }
if response.IsKissOfDeath() {
scopedLogger.Warn().
Str("kiss_code", response.KissCode).
Msg("ignoring NTP server kiss of death")
results <- nil
return
}
rtt := float64(response.RTT.Milliseconds())
// set the last RTT // set the last RTT
metricNtpServerLastRTT.WithLabelValues( metricNtpServerLastRTT.WithLabelValues(
server, server,
).Set(float64(response.RTT.Milliseconds())) ).Set(rtt)
// set the RTT histogram // set the RTT histogram
metricNtpServerRttHistogram.WithLabelValues( metricNtpServerRttHistogram.WithLabelValues(
server, server,
).Observe(float64(response.RTT.Milliseconds())) ).Observe(rtt)
// set the server info // set the server info
metricNtpServerInfo.WithLabelValues( metricNtpServerInfo.WithLabelValues(
@ -91,10 +106,13 @@ func (t *TimeSync) queryMultipleNTP(servers []string, timeout time.Duration) (no
scopedLogger.Info(). scopedLogger.Info().
Str("time", now.Format(time.RFC3339)). Str("time", now.Format(time.RFC3339)).
Str("reference", response.ReferenceString()). Str("reference", response.ReferenceString()).
Str("rtt", response.RTT.String()). Float64("rtt", rtt).
Str("clockOffset", response.ClockOffset.String()). Str("clockOffset", response.ClockOffset.String()).
Uint8("stratum", response.Stratum). Uint8("stratum", response.Stratum).
Msg("NTP server returned time") Msg("NTP server returned time")
cancel()
results <- &ntpResult{ results <- &ntpResult{
now: now, now: now,
offset: &response.ClockOffset, offset: &response.ClockOffset,

View File

@ -28,9 +28,8 @@ type TimeSync struct {
syncLock *sync.Mutex syncLock *sync.Mutex
l *zerolog.Logger l *zerolog.Logger
ntpServers []string networkConfig *network.NetworkConfig
httpUrls []string dhcpNtpAddresses []string
networkConfig *network.NetworkConfig
rtcDevicePath string rtcDevicePath string
rtcDevice *os.File //nolint:unused rtcDevice *os.File //nolint:unused
@ -64,14 +63,13 @@ func NewTimeSync(opts *TimeSyncOptions) *TimeSync {
} }
t := &TimeSync{ t := &TimeSync{
syncLock: &sync.Mutex{}, syncLock: &sync.Mutex{},
l: opts.Logger, l: opts.Logger,
rtcDevicePath: rtcDevice, dhcpNtpAddresses: []string{},
rtcLock: &sync.Mutex{}, rtcDevicePath: rtcDevice,
preCheckFunc: opts.PreCheckFunc, rtcLock: &sync.Mutex{},
ntpServers: defaultNTPServers, preCheckFunc: opts.PreCheckFunc,
httpUrls: defaultHTTPUrls, networkConfig: opts.NetworkConfig,
networkConfig: opts.NetworkConfig,
} }
if t.rtcDevicePath != "" { if t.rtcDevicePath != "" {
@ -82,34 +80,42 @@ func NewTimeSync(opts *TimeSyncOptions) *TimeSync {
return t return t
} }
func (t *TimeSync) SetDhcpNtpAddresses(addresses []string) {
t.dhcpNtpAddresses = addresses
}
func (t *TimeSync) getSyncMode() SyncMode { func (t *TimeSync) getSyncMode() SyncMode {
syncMode := SyncMode{ syncMode := SyncMode{
Ntp: true,
Http: true,
Ordering: []string{"ntp_dhcp", "ntp", "http"},
NtpUseFallback: true, NtpUseFallback: true,
HttpUseFallback: true, HttpUseFallback: true,
} }
var syncModeString string
if t.networkConfig != nil { if t.networkConfig != nil {
syncModeString = t.networkConfig.TimeSyncMode.String switch t.networkConfig.TimeSyncMode.String {
case "ntp_only":
syncMode.Http = false
case "http_only":
syncMode.Ntp = false
}
if t.networkConfig.TimeSyncDisableFallback.Bool { if t.networkConfig.TimeSyncDisableFallback.Bool {
syncMode.NtpUseFallback = false syncMode.NtpUseFallback = false
syncMode.HttpUseFallback = false syncMode.HttpUseFallback = false
} }
var syncOrdering = t.networkConfig.TimeSyncOrdering
if len(syncOrdering) > 0 {
syncMode.Ordering = syncOrdering
}
} }
switch syncModeString { t.l.Debug().Strs("Ordering", syncMode.Ordering).Bool("Ntp", syncMode.Ntp).Bool("Http", syncMode.Http).Bool("NtpUseFallback", syncMode.NtpUseFallback).Bool("HttpUseFallback", syncMode.HttpUseFallback).Msg("sync mode")
case "ntp_only":
syncMode.Ntp = true
case "http_only":
syncMode.Http = true
default:
syncMode.Ntp = true
syncMode.Http = true
}
return syncMode return syncMode
} }
func (t *TimeSync) doTimeSync() { func (t *TimeSync) doTimeSync() {
metricTimeSyncStatus.Set(0) metricTimeSyncStatus.Set(0)
for { for {
@ -154,16 +160,61 @@ func (t *TimeSync) Sync() error {
offset *time.Duration offset *time.Duration
) )
syncMode := t.getSyncMode()
metricTimeSyncCount.Inc() metricTimeSyncCount.Inc()
if syncMode.Ntp { syncMode := t.getSyncMode()
now, offset = t.queryNetworkTime()
}
if syncMode.Http && now == nil { Orders:
now = t.queryAllHttpTime() for _, mode := range syncMode.Ordering {
switch mode {
case "ntp_user_provided":
if syncMode.Ntp {
t.l.Info().Msg("using NTP custom servers")
now, offset = t.queryNetworkTime(t.networkConfig.TimeSyncNTPServers)
if now != nil {
t.l.Info().Str("source", "NTP").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "ntp_dhcp":
if syncMode.Ntp {
t.l.Info().Msg("using NTP servers from DHCP")
now, offset = t.queryNetworkTime(t.dhcpNtpAddresses)
if now != nil {
t.l.Info().Str("source", "NTP DHCP").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "ntp":
if syncMode.Ntp && syncMode.NtpUseFallback {
t.l.Info().Msg("using NTP fallback")
now, offset = t.queryNetworkTime(defaultNTPServers)
if now != nil {
t.l.Info().Str("source", "NTP fallback").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "http_user_provided":
if syncMode.Http {
t.l.Info().Msg("using HTTP custom URLs")
now = t.queryAllHttpTime(t.networkConfig.TimeSyncHTTPUrls)
if now != nil {
t.l.Info().Str("source", "HTTP").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "http":
if syncMode.Http && syncMode.HttpUseFallback {
t.l.Info().Msg("using HTTP fallback")
now = t.queryAllHttpTime(defaultHTTPUrls)
if now != nil {
t.l.Info().Str("source", "HTTP fallback").Time("now", *now).Msg("time obtained")
break Orders
}
}
default:
t.l.Warn().Str("mode", mode).Msg("unknown time sync mode, skipping")
}
} }
if now == nil { if now == nil {

View File

@ -143,7 +143,7 @@ func (u *UsbGadget) listenKeyboardEvents() {
default: default:
l.Trace().Msg("reading from keyboard") l.Trace().Msg("reading from keyboard")
if u.keyboardHidFile == nil { if u.keyboardHidFile == nil {
u.logWithSupression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil") u.logWithSuppression("keyboardHidFileNil", 100, &l, nil, "keyboardHidFile is nil")
// show the error every 100 times to avoid spamming the logs // show the error every 100 times to avoid spamming the logs
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
@ -153,7 +153,7 @@ func (u *UsbGadget) listenKeyboardEvents() {
n, err := u.keyboardHidFile.Read(buf) n, err := u.keyboardHidFile.Read(buf)
if err != nil { if err != nil {
u.logWithSupression("keyboardHidFileRead", 100, &l, err, "failed to read") u.logWithSuppression("keyboardHidFileRead", 100, &l, err, "failed to read")
continue continue
} }
u.resetLogSuppressionCounter("keyboardHidFileRead") u.resetLogSuppressionCounter("keyboardHidFileRead")
@ -201,7 +201,7 @@ func (u *UsbGadget) keyboardWriteHidFile(data []byte) error {
_, err := u.keyboardHidFile.Write(data) _, err := u.keyboardHidFile.Write(data)
if err != nil { if err != nil {
u.logWithSupression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0") u.logWithSuppression("keyboardWriteHidFile", 100, u.log, err, "failed to write to hidg0")
u.keyboardHidFile.Close() u.keyboardHidFile.Close()
u.keyboardHidFile = nil u.keyboardHidFile = nil
return err return err

View File

@ -75,7 +75,7 @@ func (u *UsbGadget) absMouseWriteHidFile(data []byte) error {
_, err := u.absMouseHidFile.Write(data) _, err := u.absMouseHidFile.Write(data)
if err != nil { if err != nil {
u.logWithSupression("absMouseWriteHidFile", 100, u.log, err, "failed to write to hidg1") u.logWithSuppression("absMouseWriteHidFile", 100, u.log, err, "failed to write to hidg1")
u.absMouseHidFile.Close() u.absMouseHidFile.Close()
u.absMouseHidFile = nil u.absMouseHidFile = nil
return err return err

View File

@ -65,7 +65,7 @@ func (u *UsbGadget) relMouseWriteHidFile(data []byte) error {
_, err := u.relMouseHidFile.Write(data) _, err := u.relMouseHidFile.Write(data)
if err != nil { if err != nil {
u.logWithSupression("relMouseWriteHidFile", 100, u.log, err, "failed to write to hidg2") u.logWithSuppression("relMouseWriteHidFile", 100, u.log, err, "failed to write to hidg2")
u.relMouseHidFile.Close() u.relMouseHidFile.Close()
u.relMouseHidFile = nil u.relMouseHidFile = nil
return err return err

View File

@ -81,7 +81,7 @@ func compareFileContent(oldContent []byte, newContent []byte, looserMatch bool)
return false return false
} }
func (u *UsbGadget) logWithSupression(counterName string, every int, logger *zerolog.Logger, err error, msg string, args ...interface{}) { func (u *UsbGadget) logWithSuppression(counterName string, every int, logger *zerolog.Logger, err error, msg string, args ...interface{}) {
u.logSuppressionLock.Lock() u.logSuppressionLock.Lock()
defer u.logSuppressionLock.Unlock() defer u.logSuppressionLock.Unlock()

View File

@ -272,6 +272,13 @@ func restartNativeBinary(binaryPath string) error {
nativeLogger.Warn().Err(err).Msg("failed to restart binary") nativeLogger.Warn().Err(err).Msg("failed to restart binary")
} }
nativeCmd = cmd nativeCmd = cmd
// reset the display state
time.Sleep(1 * time.Second)
clearDisplayState()
updateStaticContents()
requestDisplayUpdate(true)
return err return err
} }

View File

@ -19,6 +19,14 @@ func networkStateChanged() {
// do not block the main thread // do not block the main thread
go waitCtrlAndRequestDisplayUpdate(true) go waitCtrlAndRequestDisplayUpdate(true)
if timeSync != nil {
if networkState != nil {
timeSync.SetDhcpNtpAddresses(networkState.NtpAddressesString())
}
timeSync.Sync()
}
// always restart mDNS when the network state changes // always restart mDNS when the network state changes
if mDNS != nil { if mDNS != nil {
_ = mDNS.SetListenOptions(config.NetworkConfig.GetMDNSMode()) _ = mDNS.SetListenOptions(config.NetworkConfig.GetMDNSMode())