mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 15:26:40 +00:00
Compare commits
61 Commits
v0.66.0
...
feature/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55781d1e9d | ||
|
|
ab77508950 | ||
|
|
b9462f5c6b | ||
|
|
5ffaa5cdd6 | ||
|
|
a1858a9cb7 | ||
|
|
212b34f639 | ||
|
|
af8eaa23e2 | ||
|
|
f0eed50678 | ||
|
|
19d94c6158 | ||
|
|
628eb56073 | ||
|
|
a590c38d8b | ||
|
|
4e149c9222 | ||
|
|
59f5b34280 | ||
|
|
dff06d0898 | ||
|
|
80a8816b1d | ||
|
|
387e374e4b | ||
|
|
3e6baea405 | ||
|
|
fe9b844511 | ||
|
|
2e1aa497d2 | ||
|
|
529c0314f8 | ||
|
|
d86875aeac | ||
|
|
f80fe506d5 | ||
|
|
967c6f3cd3 | ||
|
|
e50e124e70 | ||
|
|
c545689448 | ||
|
|
8f389fef19 | ||
|
|
d3d6a327e0 | ||
|
|
b5489d4986 | ||
|
|
7a23c57cf8 | ||
|
|
11f891220e | ||
|
|
5585adce18 | ||
|
|
f884299823 | ||
|
|
15aa6bae1b | ||
|
|
11eb725ac8 | ||
|
|
30c02ab78c | ||
|
|
3acd86e346 | ||
|
|
5c20f13c48 | ||
|
|
e6587b071d | ||
|
|
85451ab4cd | ||
|
|
a7f3ba03eb | ||
|
|
4f0a3a77ad | ||
|
|
44655ca9b5 | ||
|
|
e601278117 | ||
|
|
8e7b016be2 | ||
|
|
9e01ea7aae | ||
|
|
cfc7ec8bb9 | ||
|
|
b3bbc0e5c6 | ||
|
|
d7c8e37ff4 | ||
|
|
05b66e73bc | ||
|
|
01ceedac89 | ||
|
|
403babd433 | ||
|
|
47133031e5 | ||
|
|
82da606886 | ||
|
|
bbe5ae2145 | ||
|
|
0b21498b39 | ||
|
|
0ca59535f1 | ||
|
|
59c77d0658 | ||
|
|
333e045099 | ||
|
|
c2c4d9d336 | ||
|
|
9a6a72e88e | ||
|
|
afe6d9fca4 |
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Community Support
|
||||
url: https://forum.netbird.io/
|
||||
about: Community support forum
|
||||
- name: Cloud Support
|
||||
url: https://docs.netbird.io/help/report-bug-issues
|
||||
about: Contact us for support
|
||||
- name: Client/Connection Troubleshooting
|
||||
url: https://docs.netbird.io/help/troubleshooting-client
|
||||
about: See our client troubleshooting guide for help addressing common issues
|
||||
- name: Self-host Troubleshooting
|
||||
url: https://docs.netbird.io/selfhosted/troubleshooting
|
||||
about: See our self-host troubleshooting guide for help addressing common issues
|
||||
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
with:
|
||||
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros,ans,deriver
|
||||
ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros,ans,deriver,te
|
||||
skip: go.mod,go.sum,**/proxy/web/**
|
||||
golangci:
|
||||
strategy:
|
||||
|
||||
51
.github/workflows/pr-title-check.yml
vendored
Normal file
51
.github/workflows/pr-title-check.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: PR Title Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
check-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate PR title prefix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const title = context.payload.pull_request.title;
|
||||
const allowedTags = [
|
||||
'management',
|
||||
'client',
|
||||
'signal',
|
||||
'proxy',
|
||||
'relay',
|
||||
'misc',
|
||||
'infrastructure',
|
||||
'self-hosted',
|
||||
'doc',
|
||||
];
|
||||
|
||||
const pattern = /^\[([^\]]+)\]\s+.+/;
|
||||
const match = title.match(pattern);
|
||||
|
||||
if (!match) {
|
||||
core.setFailed(
|
||||
`PR title must start with a tag in brackets.\n` +
|
||||
`Example: [client] fix something\n` +
|
||||
`Allowed tags: ${allowedTags.join(', ')}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = match[1].split(',').map(t => t.trim().toLowerCase());
|
||||
|
||||
const invalid = tags.filter(t => !allowedTags.includes(t));
|
||||
if (invalid.length > 0) {
|
||||
core.setFailed(
|
||||
`Invalid tag(s): ${invalid.join(', ')}\n` +
|
||||
`Allowed tags: ${allowedTags.join(', ')}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Valid PR title tags: [${tags.join(', ')}]`);
|
||||
86
.github/workflows/release.yml
vendored
86
.github/workflows/release.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
SIGN_PIPE_VER: "v0.1.1"
|
||||
GORELEASER_VER: "v2.3.2"
|
||||
GORELEASER_VER: "v2.14.3"
|
||||
PRODUCT_NAME: "NetBird"
|
||||
COPYRIGHT: "NetBird GmbH"
|
||||
|
||||
@@ -169,6 +169,13 @@ jobs:
|
||||
- name: Install OS build dependencies
|
||||
run: sudo apt update && sudo apt install -y -q gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu
|
||||
|
||||
- name: Decode GPG signing key
|
||||
env:
|
||||
GPG_RPM_PRIVATE_KEY: ${{ secrets.GPG_RPM_PRIVATE_KEY }}
|
||||
run: |
|
||||
echo "$GPG_RPM_PRIVATE_KEY" | base64 -d > /tmp/gpg-rpm-signing-key.asc
|
||||
echo "GPG_RPM_KEY_FILE=/tmp/gpg-rpm-signing-key.asc" >> $GITHUB_ENV
|
||||
|
||||
- name: Install goversioninfo
|
||||
run: go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@233067e
|
||||
- name: Generate windows syso amd64
|
||||
@@ -186,18 +193,54 @@ jobs:
|
||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
||||
UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||
- name: Tag and push PR images (amd64 only)
|
||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
|
||||
GPG_RPM_KEY_FILE: ${{ env.GPG_RPM_KEY_FILE }}
|
||||
NFPM_NETBIRD_RPM_PASSPHRASE: ${{ secrets.GPG_RPM_PASSPHRASE }}
|
||||
- name: Verify RPM signatures
|
||||
run: |
|
||||
PR_TAG="pr-${{ github.event.pull_request.number }}"
|
||||
docker run --rm -v $(pwd)/dist:/dist fedora:41 bash -c '
|
||||
dnf install -y -q rpm-sign curl >/dev/null 2>&1
|
||||
curl -sSL https://pkgs.netbird.io/yum/repodata/repomd.xml.key -o /tmp/rpm-pub.key
|
||||
rpm --import /tmp/rpm-pub.key
|
||||
echo "=== Verifying RPM signatures ==="
|
||||
for rpm_file in /dist/*amd64*.rpm; do
|
||||
[ -f "$rpm_file" ] || continue
|
||||
echo "--- $(basename $rpm_file) ---"
|
||||
rpm -K "$rpm_file"
|
||||
done
|
||||
'
|
||||
- name: Clean up GPG key
|
||||
if: always()
|
||||
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
||||
- name: Tag and push images (amd64 only)
|
||||
if: |
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/main')
|
||||
run: |
|
||||
resolve_tags() {
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
echo "pr-${{ github.event.pull_request.number }}"
|
||||
else
|
||||
echo "main sha-$(git rev-parse --short HEAD)"
|
||||
fi
|
||||
}
|
||||
|
||||
tag_and_push() {
|
||||
local src="$1" img_name tag dst
|
||||
img_name="${src%%:*}"
|
||||
for tag in $(resolve_tags); do
|
||||
dst="${img_name}:${tag}"
|
||||
echo "Tagging ${src} -> ${dst}"
|
||||
docker tag "$src" "$dst"
|
||||
docker push "$dst"
|
||||
done
|
||||
}
|
||||
|
||||
export -f tag_and_push resolve_tags
|
||||
|
||||
echo '${{ steps.goreleaser.outputs.artifacts }}' | \
|
||||
jq -r '.[] | select(.type == "Docker Image") | select(.goarch == "amd64") | .name' | \
|
||||
grep '^ghcr.io/' | while read -r SRC; do
|
||||
IMG_NAME="${SRC%%:*}"
|
||||
DST="${IMG_NAME}:${PR_TAG}"
|
||||
echo "Tagging ${SRC} -> ${DST}"
|
||||
docker tag "$SRC" "$DST"
|
||||
docker push "$DST"
|
||||
tag_and_push "$SRC"
|
||||
done
|
||||
- name: upload non tags for debug purposes
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -265,6 +308,13 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: sudo apt update && sudo apt install -y -q libappindicator3-dev gir1.2-appindicator3-0.1 libxxf86vm-dev gcc-mingw-w64-x86-64
|
||||
|
||||
- name: Decode GPG signing key
|
||||
env:
|
||||
GPG_RPM_PRIVATE_KEY: ${{ secrets.GPG_RPM_PRIVATE_KEY }}
|
||||
run: |
|
||||
echo "$GPG_RPM_PRIVATE_KEY" | base64 -d > /tmp/gpg-rpm-signing-key.asc
|
||||
echo "GPG_RPM_KEY_FILE=/tmp/gpg-rpm-signing-key.asc" >> $GITHUB_ENV
|
||||
|
||||
- name: Install LLVM-MinGW for ARM64 cross-compilation
|
||||
run: |
|
||||
cd /tmp
|
||||
@@ -289,6 +339,24 @@ jobs:
|
||||
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
|
||||
UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
|
||||
GPG_RPM_KEY_FILE: ${{ env.GPG_RPM_KEY_FILE }}
|
||||
NFPM_NETBIRD_UI_RPM_PASSPHRASE: ${{ secrets.GPG_RPM_PASSPHRASE }}
|
||||
- name: Verify RPM signatures
|
||||
run: |
|
||||
docker run --rm -v $(pwd)/dist:/dist fedora:41 bash -c '
|
||||
dnf install -y -q rpm-sign curl >/dev/null 2>&1
|
||||
curl -sSL https://pkgs.netbird.io/yum/repodata/repomd.xml.key -o /tmp/rpm-pub.key
|
||||
rpm --import /tmp/rpm-pub.key
|
||||
echo "=== Verifying RPM signatures ==="
|
||||
for rpm_file in /dist/*.rpm; do
|
||||
[ -f "$rpm_file" ] || continue
|
||||
echo "--- $(basename $rpm_file) ---"
|
||||
rpm -K "$rpm_file"
|
||||
done
|
||||
'
|
||||
- name: Clean up GPG key
|
||||
if: always()
|
||||
run: rm -f /tmp/gpg-rpm-signing-key.asc
|
||||
- name: upload non tags for debug purposes
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -171,13 +171,12 @@ nfpms:
|
||||
- maintainer: Netbird <dev@netbird.io>
|
||||
description: Netbird client.
|
||||
homepage: https://netbird.io/
|
||||
id: netbird-deb
|
||||
id: netbird_deb
|
||||
bindir: /usr/bin
|
||||
builds:
|
||||
- netbird
|
||||
formats:
|
||||
- deb
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
@@ -185,16 +184,18 @@ nfpms:
|
||||
- maintainer: Netbird <dev@netbird.io>
|
||||
description: Netbird client.
|
||||
homepage: https://netbird.io/
|
||||
id: netbird-rpm
|
||||
id: netbird_rpm
|
||||
bindir: /usr/bin
|
||||
builds:
|
||||
- netbird
|
||||
formats:
|
||||
- rpm
|
||||
|
||||
scripts:
|
||||
postinstall: "release_files/post_install.sh"
|
||||
preremove: "release_files/pre_remove.sh"
|
||||
rpm:
|
||||
signature:
|
||||
key_file: '{{ if index .Env "GPG_RPM_KEY_FILE" }}{{ .Env.GPG_RPM_KEY_FILE }}{{ end }}'
|
||||
dockers:
|
||||
- image_templates:
|
||||
- netbirdio/netbird:{{ .Version }}-amd64
|
||||
@@ -876,7 +877,7 @@ brews:
|
||||
uploads:
|
||||
- name: debian
|
||||
ids:
|
||||
- netbird-deb
|
||||
- netbird_deb
|
||||
mode: archive
|
||||
target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
||||
username: dev@wiretrustee.com
|
||||
@@ -884,7 +885,7 @@ uploads:
|
||||
|
||||
- name: yum
|
||||
ids:
|
||||
- netbird-rpm
|
||||
- netbird_rpm
|
||||
mode: archive
|
||||
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||
username: dev@wiretrustee.com
|
||||
|
||||
@@ -61,7 +61,7 @@ nfpms:
|
||||
- maintainer: Netbird <dev@netbird.io>
|
||||
description: Netbird client UI.
|
||||
homepage: https://netbird.io/
|
||||
id: netbird-ui-deb
|
||||
id: netbird_ui_deb
|
||||
package_name: netbird-ui
|
||||
builds:
|
||||
- netbird-ui
|
||||
@@ -80,7 +80,7 @@ nfpms:
|
||||
- maintainer: Netbird <dev@netbird.io>
|
||||
description: Netbird client UI.
|
||||
homepage: https://netbird.io/
|
||||
id: netbird-ui-rpm
|
||||
id: netbird_ui_rpm
|
||||
package_name: netbird-ui
|
||||
builds:
|
||||
- netbird-ui
|
||||
@@ -95,11 +95,14 @@ nfpms:
|
||||
dst: /usr/share/pixmaps/netbird.png
|
||||
dependencies:
|
||||
- netbird
|
||||
rpm:
|
||||
signature:
|
||||
key_file: '{{ if index .Env "GPG_RPM_KEY_FILE" }}{{ .Env.GPG_RPM_KEY_FILE }}{{ end }}'
|
||||
|
||||
uploads:
|
||||
- name: debian
|
||||
ids:
|
||||
- netbird-ui-deb
|
||||
- netbird_ui_deb
|
||||
mode: archive
|
||||
target: https://pkgs.wiretrustee.com/debian/pool/{{ .ArtifactName }};deb.distribution=stable;deb.component=main;deb.architecture={{ if .Arm }}armhf{{ else }}{{ .Arch }}{{ end }};deb.package=
|
||||
username: dev@wiretrustee.com
|
||||
@@ -107,7 +110,7 @@ uploads:
|
||||
|
||||
- name: yum
|
||||
ids:
|
||||
- netbird-ui-rpm
|
||||
- netbird_ui_rpm
|
||||
mode: archive
|
||||
target: https://pkgs.wiretrustee.com/yum/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||
username: dev@wiretrustee.com
|
||||
|
||||
@@ -126,6 +126,7 @@ See a complete [architecture overview](https://docs.netbird.io/about-netbird/how
|
||||
### Community projects
|
||||
- [NetBird installer script](https://github.com/physk/netbird-installer)
|
||||
- [NetBird ansible collection by Dominion Solutions](https://galaxy.ansible.com/ui/repo/published/dominion_solutions/netbird/)
|
||||
- [netbird-tui](https://github.com/n0pashkov/netbird-tui) — terminal UI for managing NetBird peers, routes, and settings
|
||||
|
||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
||||
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# sudo podman build -t localhost/netbird:latest -f client/Dockerfile --ignorefile .dockerignore-client .
|
||||
# sudo podman run --rm -it --cap-add={BPF,NET_ADMIN,NET_RAW} localhost/netbird:latest
|
||||
|
||||
FROM alpine:3.23.2
|
||||
FROM alpine:3.23.3
|
||||
# iproute2: busybox doesn't display ip rules properly
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
|
||||
@@ -124,7 +124,7 @@ func (c *Client) Run(platformFiles PlatformFiles, urlOpener URLOpener, isAndroid
|
||||
|
||||
// todo do not throw error in case of cancelled context
|
||||
ctx = internal.CtxInitState(ctx)
|
||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder, false)
|
||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||
return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener, stateFile)
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ func (c *Client) RunWithoutLogin(platformFiles PlatformFiles, dns *DNSList, dnsR
|
||||
|
||||
// todo do not throw error in case of cancelled context
|
||||
ctx = internal.CtxInitState(ctx)
|
||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder, false)
|
||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||
return c.connectClient.RunOnAndroid(c.tunAdapter, c.iFaceDiscover, c.networkChangeListener, slices.Clone(dns.items), dnsReadyListener, stateFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,20 +22,24 @@ import (
|
||||
var pinRegexp = regexp.MustCompile(`^\d{6}$`)
|
||||
|
||||
var (
|
||||
exposePin string
|
||||
exposePassword string
|
||||
exposeUserGroups []string
|
||||
exposeDomain string
|
||||
exposeNamePrefix string
|
||||
exposeProtocol string
|
||||
exposePin string
|
||||
exposePassword string
|
||||
exposeUserGroups []string
|
||||
exposeDomain string
|
||||
exposeNamePrefix string
|
||||
exposeProtocol string
|
||||
exposeExternalPort uint16
|
||||
)
|
||||
|
||||
var exposeCmd = &cobra.Command{
|
||||
Use: "expose <port>",
|
||||
Short: "Expose a local port via the NetBird reverse proxy",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: "netbird expose --with-password safe-pass 8080",
|
||||
RunE: exposeFn,
|
||||
Use: "expose <port>",
|
||||
Short: "Expose a local port via the NetBird reverse proxy",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: ` netbird expose --with-password safe-pass 8080
|
||||
netbird expose --protocol tcp 5432
|
||||
netbird expose --protocol tcp --with-external-port 5433 5432
|
||||
netbird expose --protocol tls --with-custom-domain tls.example.com 4443`,
|
||||
RunE: exposeFn,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -44,7 +48,52 @@ func init() {
|
||||
exposeCmd.Flags().StringSliceVar(&exposeUserGroups, "with-user-groups", nil, "Restrict access to specific user groups with SSO (e.g. --with-user-groups devops,Backend)")
|
||||
exposeCmd.Flags().StringVar(&exposeDomain, "with-custom-domain", "", "Custom domain for the exposed service, must be configured to your account (e.g. --with-custom-domain myapp.example.com)")
|
||||
exposeCmd.Flags().StringVar(&exposeNamePrefix, "with-name-prefix", "", "Prefix for the generated service name (e.g. --with-name-prefix my-app)")
|
||||
exposeCmd.Flags().StringVar(&exposeProtocol, "protocol", "http", "Protocol to use, http/https is supported (e.g. --protocol http)")
|
||||
exposeCmd.Flags().StringVar(&exposeProtocol, "protocol", "http", "Protocol to use: http, https, tcp, udp, or tls (e.g. --protocol tcp)")
|
||||
exposeCmd.Flags().Uint16Var(&exposeExternalPort, "with-external-port", 0, "Public-facing external port on the proxy cluster (defaults to the target port for L4)")
|
||||
}
|
||||
|
||||
// isClusterProtocol returns true for L4/TLS protocols that reject HTTP-style auth flags.
|
||||
func isClusterProtocol(protocol string) bool {
|
||||
switch strings.ToLower(protocol) {
|
||||
case "tcp", "udp", "tls":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isPortBasedProtocol returns true for pure port-based protocols (TCP/UDP)
|
||||
// where domain display doesn't apply. TLS uses SNI so it has a domain.
|
||||
func isPortBasedProtocol(protocol string) bool {
|
||||
switch strings.ToLower(protocol) {
|
||||
case "tcp", "udp":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// extractPort returns the port portion of a URL like "tcp://host:12345", or
|
||||
// falls back to the given default formatted as a string.
|
||||
func extractPort(serviceURL string, fallback uint16) string {
|
||||
u := serviceURL
|
||||
if idx := strings.Index(u, "://"); idx != -1 {
|
||||
u = u[idx+3:]
|
||||
}
|
||||
if i := strings.LastIndex(u, ":"); i != -1 {
|
||||
if p := u[i+1:]; p != "" {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return strconv.FormatUint(uint64(fallback), 10)
|
||||
}
|
||||
|
||||
// resolveExternalPort returns the effective external port, defaulting to the target port.
|
||||
func resolveExternalPort(targetPort uint64) uint16 {
|
||||
if exposeExternalPort != 0 {
|
||||
return exposeExternalPort
|
||||
}
|
||||
return uint16(targetPort)
|
||||
}
|
||||
|
||||
func validateExposeFlags(cmd *cobra.Command, portStr string) (uint64, error) {
|
||||
@@ -57,7 +106,15 @@ func validateExposeFlags(cmd *cobra.Command, portStr string) (uint64, error) {
|
||||
}
|
||||
|
||||
if !isProtocolValid(exposeProtocol) {
|
||||
return 0, fmt.Errorf("unsupported protocol %q: only 'http' or 'https' are supported", exposeProtocol)
|
||||
return 0, fmt.Errorf("unsupported protocol %q: must be http, https, tcp, udp, or tls", exposeProtocol)
|
||||
}
|
||||
|
||||
if isClusterProtocol(exposeProtocol) {
|
||||
if exposePin != "" || exposePassword != "" || len(exposeUserGroups) > 0 {
|
||||
return 0, fmt.Errorf("auth flags (--with-pin, --with-password, --with-user-groups) are not supported for %s protocol", exposeProtocol)
|
||||
}
|
||||
} else if cmd.Flags().Changed("with-external-port") {
|
||||
return 0, fmt.Errorf("--with-external-port is not supported for %s protocol", exposeProtocol)
|
||||
}
|
||||
|
||||
if exposePin != "" && !pinRegexp.MatchString(exposePin) {
|
||||
@@ -76,7 +133,12 @@ func validateExposeFlags(cmd *cobra.Command, portStr string) (uint64, error) {
|
||||
}
|
||||
|
||||
func isProtocolValid(exposeProtocol string) bool {
|
||||
return strings.ToLower(exposeProtocol) == "http" || strings.ToLower(exposeProtocol) == "https"
|
||||
switch strings.ToLower(exposeProtocol) {
|
||||
case "http", "https", "tcp", "udp", "tls":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func exposeFn(cmd *cobra.Command, args []string) error {
|
||||
@@ -123,7 +185,7 @@ func exposeFn(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
stream, err := client.ExposeService(ctx, &proto.ExposeServiceRequest{
|
||||
req := &proto.ExposeServiceRequest{
|
||||
Port: uint32(port),
|
||||
Protocol: protocol,
|
||||
Pin: exposePin,
|
||||
@@ -131,7 +193,12 @@ func exposeFn(cmd *cobra.Command, args []string) error {
|
||||
UserGroups: exposeUserGroups,
|
||||
Domain: exposeDomain,
|
||||
NamePrefix: exposeNamePrefix,
|
||||
})
|
||||
}
|
||||
if isClusterProtocol(exposeProtocol) {
|
||||
req.ListenPort = uint32(resolveExternalPort(port))
|
||||
}
|
||||
|
||||
stream, err := client.ExposeService(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expose service: %w", err)
|
||||
}
|
||||
@@ -149,8 +216,14 @@ func toExposeProtocol(exposeProtocol string) (proto.ExposeProtocol, error) {
|
||||
return proto.ExposeProtocol_EXPOSE_HTTP, nil
|
||||
case "https":
|
||||
return proto.ExposeProtocol_EXPOSE_HTTPS, nil
|
||||
case "tcp":
|
||||
return proto.ExposeProtocol_EXPOSE_TCP, nil
|
||||
case "udp":
|
||||
return proto.ExposeProtocol_EXPOSE_UDP, nil
|
||||
case "tls":
|
||||
return proto.ExposeProtocol_EXPOSE_TLS, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported protocol %q: only 'http' or 'https' are supported", exposeProtocol)
|
||||
return 0, fmt.Errorf("unsupported protocol %q: must be http, https, tcp, udp, or tls", exposeProtocol)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,20 +233,33 @@ func handleExposeReady(cmd *cobra.Command, stream proto.DaemonService_ExposeServ
|
||||
return fmt.Errorf("receive expose event: %w", err)
|
||||
}
|
||||
|
||||
switch e := event.Event.(type) {
|
||||
case *proto.ExposeServiceEvent_Ready:
|
||||
cmd.Println("Service exposed successfully!")
|
||||
cmd.Printf(" Name: %s\n", e.Ready.ServiceName)
|
||||
cmd.Printf(" URL: %s\n", e.Ready.ServiceUrl)
|
||||
cmd.Printf(" Domain: %s\n", e.Ready.Domain)
|
||||
cmd.Printf(" Protocol: %s\n", exposeProtocol)
|
||||
cmd.Printf(" Port: %d\n", port)
|
||||
cmd.Println()
|
||||
cmd.Println("Press Ctrl+C to stop exposing.")
|
||||
return nil
|
||||
default:
|
||||
ready, ok := event.Event.(*proto.ExposeServiceEvent_Ready)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected expose event: %T", event.Event)
|
||||
}
|
||||
printExposeReady(cmd, ready.Ready, port)
|
||||
return nil
|
||||
}
|
||||
|
||||
func printExposeReady(cmd *cobra.Command, r *proto.ExposeServiceReady, port uint64) {
|
||||
cmd.Println("Service exposed successfully!")
|
||||
cmd.Printf(" Name: %s\n", r.ServiceName)
|
||||
if r.ServiceUrl != "" {
|
||||
cmd.Printf(" URL: %s\n", r.ServiceUrl)
|
||||
}
|
||||
if r.Domain != "" && !isPortBasedProtocol(exposeProtocol) {
|
||||
cmd.Printf(" Domain: %s\n", r.Domain)
|
||||
}
|
||||
cmd.Printf(" Protocol: %s\n", exposeProtocol)
|
||||
cmd.Printf(" Internal: %d\n", port)
|
||||
if isClusterProtocol(exposeProtocol) {
|
||||
cmd.Printf(" External: %s\n", extractPort(r.ServiceUrl, resolveExternalPort(port)))
|
||||
}
|
||||
if r.PortAutoAssigned && exposeExternalPort != 0 {
|
||||
cmd.Printf("\n Note: requested port %d was reassigned\n", exposeExternalPort)
|
||||
}
|
||||
cmd.Println()
|
||||
cmd.Println("Press Ctrl+C to stop exposing.")
|
||||
}
|
||||
|
||||
func waitForExposeEvents(cmd *cobra.Command, ctx context.Context, stream proto.DaemonService_ExposeServiceClient) error {
|
||||
|
||||
@@ -103,7 +103,7 @@ func (p *program) Stop(srv service.Service) error {
|
||||
|
||||
// Common setup for service control commands
|
||||
func setupServiceControlCommand(cmd *cobra.Command, ctx context.Context, cancel context.CancelFunc) (service.Service, error) {
|
||||
SetFlagsFromEnvVars(rootCmd)
|
||||
// rootCmd env vars are already applied by PersistentPreRunE.
|
||||
SetFlagsFromEnvVars(serviceCmd)
|
||||
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/reposign"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/reposign"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/reposign"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/reposign"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/reposign"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/reposign"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/reposign"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/reposign"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -197,7 +197,7 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command, activeProf *pr
|
||||
r := peer.NewRecorder(config.ManagementURL.String())
|
||||
r.GetFullStatus()
|
||||
|
||||
connectClient := internal.NewConnectClient(ctx, config, r, false)
|
||||
connectClient := internal.NewConnectClient(ctx, config, r)
|
||||
SetupDebugHandler(ctx, config, r, connectClient, "")
|
||||
|
||||
return connectClient.Run(nil, util.FindFirstLogPath(logFiles))
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/installer"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/installer"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
||||
|
||||
"github.com/netbirdio/netbird/client/iface"
|
||||
"github.com/netbirdio/netbird/client/iface/netstack"
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
"github.com/netbirdio/netbird/client/internal/auth"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
sshcommon "github.com/netbirdio/netbird/client/ssh"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/shared/management/domain"
|
||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
)
|
||||
|
||||
@@ -81,6 +83,14 @@ type Options struct {
|
||||
BlockInbound bool
|
||||
// WireguardPort is the port for the WireGuard interface. Use 0 for a random port.
|
||||
WireguardPort *int
|
||||
// MTU is the MTU for the WireGuard interface.
|
||||
// Valid values are in the range 576..8192 bytes.
|
||||
// If non-nil, this value overrides any value stored in the config file.
|
||||
// If nil, the existing config MTU (if non-zero) is preserved; otherwise it defaults to 1280.
|
||||
// Set to a higher value (e.g. 1400) if carrying QUIC or other protocols that require larger datagrams.
|
||||
MTU *uint16
|
||||
// DNSLabels defines additional DNS labels configured in the peer.
|
||||
DNSLabels []string
|
||||
}
|
||||
|
||||
// validateCredentials checks that exactly one credential type is provided
|
||||
@@ -112,6 +122,12 @@ func New(opts Options) (*Client, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.MTU != nil {
|
||||
if err := iface.ValidateMTU(*opts.MTU); err != nil {
|
||||
return nil, fmt.Errorf("invalid MTU: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.LogOutput != nil {
|
||||
logrus.SetOutput(opts.LogOutput)
|
||||
}
|
||||
@@ -140,9 +156,14 @@ func New(opts Options) (*Client, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
var parsedLabels domain.List
|
||||
if parsedLabels, err = domain.FromStringList(opts.DNSLabels); err != nil {
|
||||
return nil, fmt.Errorf("invalid dns labels: %w", err)
|
||||
}
|
||||
|
||||
t := true
|
||||
var config *profilemanager.Config
|
||||
var err error
|
||||
input := profilemanager.ConfigInput{
|
||||
ConfigPath: opts.ConfigPath,
|
||||
ManagementURL: opts.ManagementURL,
|
||||
@@ -151,6 +172,8 @@ func New(opts Options) (*Client, error) {
|
||||
DisableClientRoutes: &opts.DisableClientRoutes,
|
||||
BlockInbound: &opts.BlockInbound,
|
||||
WireguardPort: opts.WireguardPort,
|
||||
MTU: opts.MTU,
|
||||
DNSLabels: parsedLabels,
|
||||
}
|
||||
if opts.ConfigPath != "" {
|
||||
config, err = profilemanager.UpdateOrCreateConfig(input)
|
||||
@@ -202,7 +225,7 @@ func (c *Client) Start(startCtx context.Context) error {
|
||||
if err, _ := authClient.Login(ctx, c.setupKey, c.jwtToken); err != nil {
|
||||
return fmt.Errorf("login: %w", err)
|
||||
}
|
||||
client := internal.NewConnectClient(ctx, c.config, c.recorder, false)
|
||||
client := internal.NewConnectClient(ctx, c.config, c.recorder)
|
||||
client.SetSyncResponsePersistence(true)
|
||||
|
||||
// either startup error (permanent backoff err) or nil err (successful engine up)
|
||||
|
||||
@@ -23,9 +23,10 @@ type Manager struct {
|
||||
|
||||
wgIface iFaceMapper
|
||||
|
||||
ipv4Client *iptables.IPTables
|
||||
aclMgr *aclManager
|
||||
router *router
|
||||
ipv4Client *iptables.IPTables
|
||||
aclMgr *aclManager
|
||||
router *router
|
||||
rawSupported bool
|
||||
}
|
||||
|
||||
// iFaceMapper defines subset methods of interface required for manager
|
||||
@@ -84,7 +85,7 @@ func (m *Manager) Init(stateManager *statemanager.Manager) error {
|
||||
}
|
||||
|
||||
if err := m.initNoTrackChain(); err != nil {
|
||||
return fmt.Errorf("init notrack chain: %w", err)
|
||||
log.Warnf("raw table not available, notrack rules will be disabled: %v", err)
|
||||
}
|
||||
|
||||
// persist early to ensure cleanup of chains
|
||||
@@ -318,6 +319,10 @@ func (m *Manager) SetupEBPFProxyNoTrack(proxyPort, wgPort uint16) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if !m.rawSupported {
|
||||
return fmt.Errorf("raw table not available")
|
||||
}
|
||||
|
||||
wgPortStr := fmt.Sprintf("%d", wgPort)
|
||||
proxyPortStr := fmt.Sprintf("%d", proxyPort)
|
||||
|
||||
@@ -375,12 +380,16 @@ func (m *Manager) initNoTrackChain() error {
|
||||
return fmt.Errorf("add prerouting jump rule: %w", err)
|
||||
}
|
||||
|
||||
m.rawSupported = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) cleanupNoTrackChain() error {
|
||||
exists, err := m.ipv4Client.ChainExists(tableRaw, chainNameRaw)
|
||||
if err != nil {
|
||||
if !m.rawSupported {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("check chain exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
@@ -401,6 +410,7 @@ func (m *Manager) cleanupNoTrackChain() error {
|
||||
return fmt.Errorf("clear and delete chain: %w", err)
|
||||
}
|
||||
|
||||
m.rawSupported = false
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func (m *Manager) Init(stateManager *statemanager.Manager) error {
|
||||
}
|
||||
|
||||
if err := m.initNoTrackChains(workTable); err != nil {
|
||||
return fmt.Errorf("init notrack chains: %w", err)
|
||||
log.Warnf("raw priority chains not available, notrack rules will be disabled: %v", err)
|
||||
}
|
||||
|
||||
stateManager.RegisterState(&ShutdownState{})
|
||||
|
||||
@@ -28,7 +28,7 @@ func Backoff(ctx context.Context) backoff.BackOff {
|
||||
|
||||
// CreateConnection creates a gRPC client connection with the appropriate transport options.
|
||||
// The component parameter specifies the WebSocket proxy component path (e.g., "/management", "/signal").
|
||||
func CreateConnection(ctx context.Context, addr string, tlsEnabled bool, component string) (*grpc.ClientConn, error) {
|
||||
func CreateConnection(ctx context.Context, addr string, tlsEnabled bool, component string, extraOpts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||
transportOption := grpc.WithTransportCredentials(insecure.NewCredentials())
|
||||
// for js, the outer websocket layer takes care of tls
|
||||
if tlsEnabled && runtime.GOOS != "js" {
|
||||
@@ -46,9 +46,7 @@ func CreateConnection(ctx context.Context, addr string, tlsEnabled bool, compone
|
||||
connCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
conn, err := grpc.DialContext(
|
||||
connCtx,
|
||||
addr,
|
||||
opts := []grpc.DialOption{
|
||||
transportOption,
|
||||
WithCustomDialer(tlsEnabled, component),
|
||||
grpc.WithBlock(),
|
||||
@@ -56,7 +54,10 @@ func CreateConnection(ctx context.Context, addr string, tlsEnabled bool, compone
|
||||
Time: 30 * time.Second,
|
||||
Timeout: 10 * time.Second,
|
||||
}),
|
||||
)
|
||||
}
|
||||
opts = append(opts, extraOpts...)
|
||||
|
||||
conn, err := grpc.DialContext(connCtx, addr, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial context: %w", err)
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/installer"
|
||||
"github.com/netbirdio/netbird/client/internal/updater"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/installer"
|
||||
nbnet "github.com/netbirdio/netbird/client/net"
|
||||
cProto "github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/ssh"
|
||||
@@ -44,13 +44,13 @@ import (
|
||||
)
|
||||
|
||||
type ConnectClient struct {
|
||||
ctx context.Context
|
||||
config *profilemanager.Config
|
||||
statusRecorder *peer.Status
|
||||
doInitialAutoUpdate bool
|
||||
ctx context.Context
|
||||
config *profilemanager.Config
|
||||
statusRecorder *peer.Status
|
||||
|
||||
engine *Engine
|
||||
engineMutex sync.Mutex
|
||||
engine *Engine
|
||||
engineMutex sync.Mutex
|
||||
updateManager *updater.Manager
|
||||
|
||||
persistSyncResponse bool
|
||||
}
|
||||
@@ -59,17 +59,19 @@ func NewConnectClient(
|
||||
ctx context.Context,
|
||||
config *profilemanager.Config,
|
||||
statusRecorder *peer.Status,
|
||||
doInitalAutoUpdate bool,
|
||||
) *ConnectClient {
|
||||
return &ConnectClient{
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
statusRecorder: statusRecorder,
|
||||
doInitialAutoUpdate: doInitalAutoUpdate,
|
||||
engineMutex: sync.Mutex{},
|
||||
ctx: ctx,
|
||||
config: config,
|
||||
statusRecorder: statusRecorder,
|
||||
engineMutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ConnectClient) SetUpdateManager(um *updater.Manager) {
|
||||
c.updateManager = um
|
||||
}
|
||||
|
||||
// Run with main logic.
|
||||
func (c *ConnectClient) Run(runningChan chan struct{}, logPath string) error {
|
||||
return c.run(MobileDependency{}, runningChan, logPath)
|
||||
@@ -187,14 +189,13 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
||||
stateManager := statemanager.New(path)
|
||||
stateManager.RegisterState(&sshconfig.ShutdownState{})
|
||||
|
||||
updateManager, err := updatemanager.NewManager(c.statusRecorder, stateManager)
|
||||
if err == nil {
|
||||
updateManager.CheckUpdateSuccess(c.ctx)
|
||||
if c.updateManager != nil {
|
||||
c.updateManager.CheckUpdateSuccess(c.ctx)
|
||||
}
|
||||
|
||||
inst := installer.New()
|
||||
if err := inst.CleanUpInstallerFiles(); err != nil {
|
||||
log.Errorf("failed to clean up temporary installer file: %v", err)
|
||||
}
|
||||
inst := installer.New()
|
||||
if err := inst.CleanUpInstallerFiles(); err != nil {
|
||||
log.Errorf("failed to clean up temporary installer file: %v", err)
|
||||
}
|
||||
|
||||
defer c.statusRecorder.ClientStop()
|
||||
@@ -308,7 +309,15 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
||||
checks := loginResp.GetChecks()
|
||||
|
||||
c.engineMutex.Lock()
|
||||
engine := NewEngine(engineCtx, cancel, signalClient, mgmClient, relayManager, engineConfig, mobileDependency, c.statusRecorder, checks, stateManager)
|
||||
engine := NewEngine(engineCtx, cancel, engineConfig, EngineServices{
|
||||
SignalClient: signalClient,
|
||||
MgmClient: mgmClient,
|
||||
RelayManager: relayManager,
|
||||
StatusRecorder: c.statusRecorder,
|
||||
Checks: checks,
|
||||
StateManager: stateManager,
|
||||
UpdateManager: c.updateManager,
|
||||
}, mobileDependency)
|
||||
engine.SetSyncResponsePersistence(c.persistSyncResponse)
|
||||
c.engine = engine
|
||||
c.engineMutex.Unlock()
|
||||
@@ -318,21 +327,15 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
|
||||
return wrapErr(err)
|
||||
}
|
||||
|
||||
if loginResp.PeerConfig != nil && loginResp.PeerConfig.AutoUpdate != nil {
|
||||
// AutoUpdate will be true when the user click on "Connect" menu on the UI
|
||||
if c.doInitialAutoUpdate {
|
||||
log.Infof("start engine by ui, run auto-update check")
|
||||
c.engine.InitialUpdateHandling(loginResp.PeerConfig.AutoUpdate)
|
||||
c.doInitialAutoUpdate = false
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Netbird engine started, the IP is: %s", peerConfig.GetAddress())
|
||||
state.Set(StatusConnected)
|
||||
|
||||
if runningChan != nil {
|
||||
close(runningChan)
|
||||
runningChan = nil
|
||||
select {
|
||||
case <-runningChan:
|
||||
default:
|
||||
close(runningChan)
|
||||
}
|
||||
}
|
||||
|
||||
<-engineCtx.Done()
|
||||
|
||||
@@ -49,7 +49,7 @@ func ResolveUnixDaemonAddr(addr string) string {
|
||||
switch len(found) {
|
||||
case 1:
|
||||
resolved := "unix://" + found[0]
|
||||
log.Infof("Default daemon socket not found, using discovered socket: %s", resolved)
|
||||
log.Debugf("Default daemon socket not found, using discovered socket: %s", resolved)
|
||||
return resolved
|
||||
case 0:
|
||||
return addr
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/netbirdio/netbird/client/anonymize"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/installer"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/installer"
|
||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||
mgmProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
|
||||
@@ -77,7 +77,7 @@ func (d *Resolver) ID() types.HandlerID {
|
||||
return "local-resolver"
|
||||
}
|
||||
|
||||
func (d *Resolver) ProbeAvailability() {}
|
||||
func (d *Resolver) ProbeAvailability(context.Context) {}
|
||||
|
||||
// ServeDNS handles a DNS request
|
||||
func (d *Resolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||
|
||||
@@ -104,12 +104,16 @@ type DefaultServer struct {
|
||||
|
||||
statusRecorder *peer.Status
|
||||
stateManager *statemanager.Manager
|
||||
|
||||
probeMu sync.Mutex
|
||||
probeCancel context.CancelFunc
|
||||
probeWg sync.WaitGroup
|
||||
}
|
||||
|
||||
type handlerWithStop interface {
|
||||
dns.Handler
|
||||
Stop()
|
||||
ProbeAvailability()
|
||||
ProbeAvailability(context.Context)
|
||||
ID() types.HandlerID
|
||||
}
|
||||
|
||||
@@ -362,7 +366,13 @@ func (s *DefaultServer) DnsIP() netip.Addr {
|
||||
|
||||
// Stop stops the server
|
||||
func (s *DefaultServer) Stop() {
|
||||
s.probeMu.Lock()
|
||||
if s.probeCancel != nil {
|
||||
s.probeCancel()
|
||||
}
|
||||
s.ctxCancel()
|
||||
s.probeMu.Unlock()
|
||||
s.probeWg.Wait()
|
||||
s.shutdownWg.Wait()
|
||||
|
||||
s.mux.Lock()
|
||||
@@ -479,7 +489,8 @@ func (s *DefaultServer) SearchDomains() []string {
|
||||
}
|
||||
|
||||
// ProbeAvailability tests each upstream group's servers for availability
|
||||
// and deactivates the group if no server responds
|
||||
// and deactivates the group if no server responds.
|
||||
// If a previous probe is still running, it will be cancelled before starting a new one.
|
||||
func (s *DefaultServer) ProbeAvailability() {
|
||||
if val := os.Getenv(envSkipDNSProbe); val != "" {
|
||||
skipProbe, err := strconv.ParseBool(val)
|
||||
@@ -492,15 +503,52 @@ func (s *DefaultServer) ProbeAvailability() {
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, mux := range s.dnsMuxMap {
|
||||
wg.Add(1)
|
||||
go func(mux handlerWithStop) {
|
||||
defer wg.Done()
|
||||
mux.ProbeAvailability()
|
||||
}(mux.handler)
|
||||
s.probeMu.Lock()
|
||||
|
||||
// don't start probes on a stopped server
|
||||
if s.ctx.Err() != nil {
|
||||
s.probeMu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// cancel any running probe
|
||||
if s.probeCancel != nil {
|
||||
s.probeCancel()
|
||||
s.probeCancel = nil
|
||||
}
|
||||
|
||||
// wait for the previous probe goroutines to finish while holding
|
||||
// the mutex so no other caller can start a new probe concurrently
|
||||
s.probeWg.Wait()
|
||||
|
||||
// start a new probe
|
||||
probeCtx, probeCancel := context.WithCancel(s.ctx)
|
||||
s.probeCancel = probeCancel
|
||||
|
||||
s.probeWg.Add(1)
|
||||
defer s.probeWg.Done()
|
||||
|
||||
// Snapshot handlers under s.mux to avoid racing with updateMux/dnsMuxMap writers.
|
||||
s.mux.Lock()
|
||||
handlers := make([]handlerWithStop, 0, len(s.dnsMuxMap))
|
||||
for _, mux := range s.dnsMuxMap {
|
||||
handlers = append(handlers, mux.handler)
|
||||
}
|
||||
s.mux.Unlock()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, handler := range handlers {
|
||||
wg.Add(1)
|
||||
go func(h handlerWithStop) {
|
||||
defer wg.Done()
|
||||
h.ProbeAvailability(probeCtx)
|
||||
}(handler)
|
||||
}
|
||||
|
||||
s.probeMu.Unlock()
|
||||
|
||||
wg.Wait()
|
||||
probeCancel()
|
||||
}
|
||||
|
||||
func (s *DefaultServer) UpdateServerConfig(domains dnsconfig.ServerDomains) error {
|
||||
|
||||
@@ -1065,7 +1065,7 @@ type mockHandler struct {
|
||||
|
||||
func (m *mockHandler) ServeDNS(dns.ResponseWriter, *dns.Msg) {}
|
||||
func (m *mockHandler) Stop() {}
|
||||
func (m *mockHandler) ProbeAvailability() {}
|
||||
func (m *mockHandler) ProbeAvailability(context.Context) {}
|
||||
func (m *mockHandler) ID() types.HandlerID { return types.HandlerID(m.Id) }
|
||||
|
||||
type mockService struct{}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -69,7 +70,7 @@ func (s *serviceViaListener) Listen() error {
|
||||
return fmt.Errorf("eval listen address: %w", err)
|
||||
}
|
||||
s.listenIP = s.listenIP.Unmap()
|
||||
s.server.Addr = fmt.Sprintf("%s:%d", s.listenIP, s.listenPort)
|
||||
s.server.Addr = net.JoinHostPort(s.listenIP.String(), strconv.Itoa(int(s.listenPort)))
|
||||
log.Debugf("starting dns on %s", s.server.Addr)
|
||||
go func() {
|
||||
s.setListenerStatus(true)
|
||||
@@ -186,7 +187,7 @@ func (s *serviceViaListener) testFreePort(port int) (netip.Addr, bool) {
|
||||
}
|
||||
|
||||
func (s *serviceViaListener) tryToBind(ip netip.Addr, port int) bool {
|
||||
addrString := fmt.Sprintf("%s:%d", ip, port)
|
||||
addrString := net.JoinHostPort(ip.String(), strconv.Itoa(port))
|
||||
udpAddr := net.UDPAddrFromAddrPort(netip.MustParseAddrPort(addrString))
|
||||
probeListener, err := net.ListenUDP("udp", udpAddr)
|
||||
if err != nil {
|
||||
|
||||
@@ -65,6 +65,7 @@ type upstreamResolverBase struct {
|
||||
mutex sync.Mutex
|
||||
reactivatePeriod time.Duration
|
||||
upstreamTimeout time.Duration
|
||||
wg sync.WaitGroup
|
||||
|
||||
deactivate func(error)
|
||||
reactivate func()
|
||||
@@ -115,6 +116,11 @@ func (u *upstreamResolverBase) MatchSubdomains() bool {
|
||||
func (u *upstreamResolverBase) Stop() {
|
||||
log.Debugf("stopping serving DNS for upstreams %s", u.upstreamServers)
|
||||
u.cancel()
|
||||
|
||||
u.mutex.Lock()
|
||||
u.wg.Wait()
|
||||
u.mutex.Unlock()
|
||||
|
||||
}
|
||||
|
||||
// ServeDNS handles a DNS request
|
||||
@@ -260,16 +266,10 @@ func formatFailures(failures []upstreamFailure) string {
|
||||
|
||||
// ProbeAvailability tests all upstream servers simultaneously and
|
||||
// disables the resolver if none work
|
||||
func (u *upstreamResolverBase) ProbeAvailability() {
|
||||
func (u *upstreamResolverBase) ProbeAvailability(ctx context.Context) {
|
||||
u.mutex.Lock()
|
||||
defer u.mutex.Unlock()
|
||||
|
||||
select {
|
||||
case <-u.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// avoid probe if upstreams could resolve at least one query
|
||||
if u.successCount.Load() > 0 {
|
||||
return
|
||||
@@ -279,31 +279,39 @@ func (u *upstreamResolverBase) ProbeAvailability() {
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
var errors *multierror.Error
|
||||
var errs *multierror.Error
|
||||
for _, upstream := range u.upstreamServers {
|
||||
upstream := upstream
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
go func(upstream netip.AddrPort) {
|
||||
defer wg.Done()
|
||||
err := u.testNameserver(upstream, 500*time.Millisecond)
|
||||
err := u.testNameserver(u.ctx, ctx, upstream, 500*time.Millisecond)
|
||||
if err != nil {
|
||||
errors = multierror.Append(errors, err)
|
||||
mu.Lock()
|
||||
errs = multierror.Append(errs, err)
|
||||
mu.Unlock()
|
||||
log.Warnf("probing upstream nameserver %s: %s", upstream, err)
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
success = true
|
||||
}()
|
||||
mu.Unlock()
|
||||
}(upstream)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-u.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// didn't find a working upstream server, let's disable and try later
|
||||
if !success {
|
||||
u.disable(errors.ErrorOrNil())
|
||||
u.disable(errs.ErrorOrNil())
|
||||
|
||||
if u.statusRecorder == nil {
|
||||
return
|
||||
@@ -339,7 +347,7 @@ func (u *upstreamResolverBase) waitUntilResponse() {
|
||||
}
|
||||
|
||||
for _, upstream := range u.upstreamServers {
|
||||
if err := u.testNameserver(upstream, probeTimeout); err != nil {
|
||||
if err := u.testNameserver(u.ctx, nil, upstream, probeTimeout); err != nil {
|
||||
log.Tracef("upstream check for %s: %s", upstream, err)
|
||||
} else {
|
||||
// at least one upstream server is available, stop probing
|
||||
@@ -364,7 +372,9 @@ func (u *upstreamResolverBase) waitUntilResponse() {
|
||||
log.Infof("upstreams %s are responsive again. Adding them back to system", u.upstreamServersString())
|
||||
u.successCount.Add(1)
|
||||
u.reactivate()
|
||||
u.mutex.Lock()
|
||||
u.disabled = false
|
||||
u.mutex.Unlock()
|
||||
}
|
||||
|
||||
// isTimeout returns true if the given error is a network timeout error.
|
||||
@@ -387,7 +397,11 @@ func (u *upstreamResolverBase) disable(err error) {
|
||||
u.successCount.Store(0)
|
||||
u.deactivate(err)
|
||||
u.disabled = true
|
||||
go u.waitUntilResponse()
|
||||
u.wg.Add(1)
|
||||
go func() {
|
||||
defer u.wg.Done()
|
||||
u.waitUntilResponse()
|
||||
}()
|
||||
}
|
||||
|
||||
func (u *upstreamResolverBase) upstreamServersString() string {
|
||||
@@ -398,13 +412,18 @@ func (u *upstreamResolverBase) upstreamServersString() string {
|
||||
return strings.Join(servers, ", ")
|
||||
}
|
||||
|
||||
func (u *upstreamResolverBase) testNameserver(server netip.AddrPort, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(u.ctx, timeout)
|
||||
func (u *upstreamResolverBase) testNameserver(baseCtx context.Context, externalCtx context.Context, server netip.AddrPort, timeout time.Duration) error {
|
||||
mergedCtx, cancel := context.WithTimeout(baseCtx, timeout)
|
||||
defer cancel()
|
||||
|
||||
if externalCtx != nil {
|
||||
stop2 := context.AfterFunc(externalCtx, cancel)
|
||||
defer stop2()
|
||||
}
|
||||
|
||||
r := new(dns.Msg).SetQuestion(testRecord, dns.TypeSOA)
|
||||
|
||||
_, _, err := u.upstreamClient.exchange(ctx, server.String(), r)
|
||||
_, _, err := u.upstreamClient.exchange(mergedCtx, server.String(), r)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ func TestUpstreamResolver_DeactivationReactivation(t *testing.T) {
|
||||
reactivated = true
|
||||
}
|
||||
|
||||
resolver.ProbeAvailability()
|
||||
resolver.ProbeAvailability(context.TODO())
|
||||
|
||||
if !failed {
|
||||
t.Errorf("expected that resolving was deactivated")
|
||||
|
||||
@@ -51,7 +51,7 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/updater"
|
||||
"github.com/netbirdio/netbird/client/jobexec"
|
||||
cProto "github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
@@ -79,7 +79,6 @@ const (
|
||||
|
||||
var ErrResetConnection = fmt.Errorf("reset connection")
|
||||
|
||||
// EngineConfig is a config for the Engine
|
||||
type EngineConfig struct {
|
||||
WgPort int
|
||||
WgIfaceName string
|
||||
@@ -141,6 +140,17 @@ type EngineConfig struct {
|
||||
LogPath string
|
||||
}
|
||||
|
||||
// EngineServices holds the external service dependencies required by the Engine.
|
||||
type EngineServices struct {
|
||||
SignalClient signal.Client
|
||||
MgmClient mgm.Client
|
||||
RelayManager *relayClient.Manager
|
||||
StatusRecorder *peer.Status
|
||||
Checks []*mgmProto.Checks
|
||||
StateManager *statemanager.Manager
|
||||
UpdateManager *updater.Manager
|
||||
}
|
||||
|
||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||
type Engine struct {
|
||||
// signal is a Signal Service client
|
||||
@@ -209,7 +219,7 @@ type Engine struct {
|
||||
flowManager nftypes.FlowManager
|
||||
|
||||
// auto-update
|
||||
updateManager *updatemanager.Manager
|
||||
updateManager *updater.Manager
|
||||
|
||||
// WireGuard interface monitor
|
||||
wgIfaceMonitor *WGIfaceMonitor
|
||||
@@ -239,22 +249,17 @@ type localIpUpdater interface {
|
||||
func NewEngine(
|
||||
clientCtx context.Context,
|
||||
clientCancel context.CancelFunc,
|
||||
signalClient signal.Client,
|
||||
mgmClient mgm.Client,
|
||||
relayManager *relayClient.Manager,
|
||||
config *EngineConfig,
|
||||
services EngineServices,
|
||||
mobileDep MobileDependency,
|
||||
statusRecorder *peer.Status,
|
||||
checks []*mgmProto.Checks,
|
||||
stateManager *statemanager.Manager,
|
||||
) *Engine {
|
||||
engine := &Engine{
|
||||
clientCtx: clientCtx,
|
||||
clientCancel: clientCancel,
|
||||
signal: signalClient,
|
||||
signaler: peer.NewSignaler(signalClient, config.WgPrivateKey),
|
||||
mgmClient: mgmClient,
|
||||
relayManager: relayManager,
|
||||
signal: services.SignalClient,
|
||||
signaler: peer.NewSignaler(services.SignalClient, config.WgPrivateKey),
|
||||
mgmClient: services.MgmClient,
|
||||
relayManager: services.RelayManager,
|
||||
peerStore: peerstore.NewConnStore(),
|
||||
syncMsgMux: &sync.Mutex{},
|
||||
config: config,
|
||||
@@ -262,11 +267,12 @@ func NewEngine(
|
||||
STUNs: []*stun.URI{},
|
||||
TURNs: []*stun.URI{},
|
||||
networkSerial: 0,
|
||||
statusRecorder: statusRecorder,
|
||||
stateManager: stateManager,
|
||||
checks: checks,
|
||||
statusRecorder: services.StatusRecorder,
|
||||
stateManager: services.StateManager,
|
||||
checks: services.Checks,
|
||||
probeStunTurn: relay.NewStunTurnProbe(relay.DefaultCacheTTL),
|
||||
jobExecutor: jobexec.NewExecutor(),
|
||||
updateManager: services.UpdateManager,
|
||||
}
|
||||
|
||||
log.Infof("I am: %s", config.WgPrivateKey.PublicKey().String())
|
||||
@@ -309,7 +315,7 @@ func (e *Engine) Stop() error {
|
||||
}
|
||||
|
||||
if e.updateManager != nil {
|
||||
e.updateManager.Stop()
|
||||
e.updateManager.SetDownloadOnly()
|
||||
}
|
||||
|
||||
log.Info("cleaning up status recorder states")
|
||||
@@ -559,13 +565,6 @@ func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) InitialUpdateHandling(autoUpdateSettings *mgmProto.AutoUpdateSettings) {
|
||||
e.syncMsgMux.Lock()
|
||||
defer e.syncMsgMux.Unlock()
|
||||
|
||||
e.handleAutoUpdateVersion(autoUpdateSettings, true)
|
||||
}
|
||||
|
||||
func (e *Engine) createFirewall() error {
|
||||
if e.config.DisableFirewall {
|
||||
log.Infof("firewall is disabled")
|
||||
@@ -793,39 +792,22 @@ func (e *Engine) PopulateNetbirdConfig(netbirdConfig *mgmProto.NetbirdConfig, mg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) handleAutoUpdateVersion(autoUpdateSettings *mgmProto.AutoUpdateSettings, initialCheck bool) {
|
||||
func (e *Engine) handleAutoUpdateVersion(autoUpdateSettings *mgmProto.AutoUpdateSettings) {
|
||||
if e.updateManager == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if autoUpdateSettings == nil {
|
||||
return
|
||||
}
|
||||
|
||||
disabled := autoUpdateSettings.Version == disableAutoUpdate
|
||||
|
||||
// stop and cleanup if disabled
|
||||
if e.updateManager != nil && disabled {
|
||||
log.Infof("auto-update is disabled, stopping update manager")
|
||||
e.updateManager.Stop()
|
||||
e.updateManager = nil
|
||||
if autoUpdateSettings.Version == disableAutoUpdate {
|
||||
log.Infof("auto-update is disabled")
|
||||
e.updateManager.SetDownloadOnly()
|
||||
return
|
||||
}
|
||||
|
||||
// Skip check unless AlwaysUpdate is enabled or this is the initial check at startup
|
||||
if !autoUpdateSettings.AlwaysUpdate && !initialCheck {
|
||||
log.Debugf("skipping auto-update check, AlwaysUpdate is false and this is not the initial check")
|
||||
return
|
||||
}
|
||||
|
||||
// Start manager if needed
|
||||
if e.updateManager == nil {
|
||||
log.Infof("starting auto-update manager")
|
||||
updateManager, err := updatemanager.NewManager(e.statusRecorder, e.stateManager)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.updateManager = updateManager
|
||||
e.updateManager.Start(e.ctx)
|
||||
}
|
||||
log.Infof("handling auto-update version: %s", autoUpdateSettings.Version)
|
||||
e.updateManager.SetVersion(autoUpdateSettings.Version)
|
||||
e.updateManager.SetVersion(autoUpdateSettings.Version, autoUpdateSettings.AlwaysUpdate)
|
||||
}
|
||||
|
||||
func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
||||
@@ -842,7 +824,7 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
|
||||
}
|
||||
|
||||
if update.NetworkMap != nil && update.NetworkMap.PeerConfig != nil {
|
||||
e.handleAutoUpdateVersion(update.NetworkMap.PeerConfig.AutoUpdate, false)
|
||||
e.handleAutoUpdateVersion(update.NetworkMap.PeerConfig.AutoUpdate)
|
||||
}
|
||||
|
||||
if update.GetNetbirdConfig() != nil {
|
||||
@@ -1007,10 +989,11 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error {
|
||||
return errors.New("wireguard interface is not initialized")
|
||||
}
|
||||
|
||||
// Cannot update the IP address without restarting the engine because
|
||||
// the firewall, route manager, and other components cache the old address
|
||||
if e.wgInterface.Address().String() != conf.Address {
|
||||
log.Infof("peer IP address has changed from %s to %s", e.wgInterface.Address().String(), conf.Address)
|
||||
log.Infof("peer IP address changed from %s to %s, restarting client", e.wgInterface.Address().String(), conf.Address)
|
||||
_ = CtxGetState(e.ctx).Wrap(ErrResetConnection)
|
||||
e.clientCancel()
|
||||
return ErrResetConnection
|
||||
}
|
||||
|
||||
if conf.GetSshConfig() != nil {
|
||||
@@ -1315,8 +1298,7 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
|
||||
|
||||
// Test received (upstream) servers for availability right away instead of upon usage.
|
||||
// If no server of a server group responds this will disable the respective handler and retry later.
|
||||
e.dnsServer.ProbeAvailability()
|
||||
|
||||
go e.dnsServer.ProbeAvailability()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -251,9 +251,6 @@ func TestEngine_SSH(t *testing.T) {
|
||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||
engine := NewEngine(
|
||||
ctx, cancel,
|
||||
&signal.MockClient{},
|
||||
&mgmt.MockClient{},
|
||||
relayMgr,
|
||||
&EngineConfig{
|
||||
WgIfaceName: "utun101",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
@@ -263,10 +260,13 @@ func TestEngine_SSH(t *testing.T) {
|
||||
MTU: iface.DefaultMTU,
|
||||
SSHKey: sshKey,
|
||||
},
|
||||
EngineServices{
|
||||
SignalClient: &signal.MockClient{},
|
||||
MgmClient: &mgmt.MockClient{},
|
||||
RelayManager: relayMgr,
|
||||
StatusRecorder: peer.NewRecorder("https://mgm"),
|
||||
},
|
||||
MobileDependency{},
|
||||
peer.NewRecorder("https://mgm"),
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
engine.dnsServer = &dns.MockServer{
|
||||
@@ -428,13 +428,18 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, relayMgr, &EngineConfig{
|
||||
engine := NewEngine(ctx, cancel, &EngineConfig{
|
||||
WgIfaceName: "utun102",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
MTU: iface.DefaultMTU,
|
||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||
}, EngineServices{
|
||||
SignalClient: &signal.MockClient{},
|
||||
MgmClient: &mgmt.MockClient{},
|
||||
RelayManager: relayMgr,
|
||||
StatusRecorder: peer.NewRecorder("https://mgm"),
|
||||
}, MobileDependency{})
|
||||
|
||||
wgIface := &MockWGIface{
|
||||
NameFunc: func() string { return "utun102" },
|
||||
@@ -647,13 +652,18 @@ func TestEngine_Sync(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{SyncFunc: syncFunc}, relayMgr, &EngineConfig{
|
||||
engine := NewEngine(ctx, cancel, &EngineConfig{
|
||||
WgIfaceName: "utun103",
|
||||
WgAddr: "100.64.0.1/24",
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
MTU: iface.DefaultMTU,
|
||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||
}, EngineServices{
|
||||
SignalClient: &signal.MockClient{},
|
||||
MgmClient: &mgmt.MockClient{SyncFunc: syncFunc},
|
||||
RelayManager: relayMgr,
|
||||
StatusRecorder: peer.NewRecorder("https://mgm"),
|
||||
}, MobileDependency{})
|
||||
engine.ctx = ctx
|
||||
|
||||
engine.dnsServer = &dns.MockServer{
|
||||
@@ -812,13 +822,18 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
|
||||
wgAddr := fmt.Sprintf("100.66.%d.1/24", n)
|
||||
|
||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, relayMgr, &EngineConfig{
|
||||
engine := NewEngine(ctx, cancel, &EngineConfig{
|
||||
WgIfaceName: wgIfaceName,
|
||||
WgAddr: wgAddr,
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
MTU: iface.DefaultMTU,
|
||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||
}, EngineServices{
|
||||
SignalClient: &signal.MockClient{},
|
||||
MgmClient: &mgmt.MockClient{},
|
||||
RelayManager: relayMgr,
|
||||
StatusRecorder: peer.NewRecorder("https://mgm"),
|
||||
}, MobileDependency{})
|
||||
engine.ctx = ctx
|
||||
newNet, err := stdnet.NewNet(context.Background(), nil)
|
||||
if err != nil {
|
||||
@@ -1014,13 +1029,18 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
|
||||
wgAddr := fmt.Sprintf("100.66.%d.1/24", n)
|
||||
|
||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||
engine := NewEngine(ctx, cancel, &signal.MockClient{}, &mgmt.MockClient{}, relayMgr, &EngineConfig{
|
||||
engine := NewEngine(ctx, cancel, &EngineConfig{
|
||||
WgIfaceName: wgIfaceName,
|
||||
WgAddr: wgAddr,
|
||||
WgPrivateKey: key,
|
||||
WgPort: 33100,
|
||||
MTU: iface.DefaultMTU,
|
||||
}, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil)
|
||||
}, EngineServices{
|
||||
SignalClient: &signal.MockClient{},
|
||||
MgmClient: &mgmt.MockClient{},
|
||||
RelayManager: relayMgr,
|
||||
StatusRecorder: peer.NewRecorder("https://mgm"),
|
||||
}, MobileDependency{})
|
||||
engine.ctx = ctx
|
||||
|
||||
newNet, err := stdnet.NewNet(context.Background(), nil)
|
||||
@@ -1546,7 +1566,12 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
|
||||
}
|
||||
|
||||
relayMgr := relayClient.NewManager(ctx, nil, key.PublicKey().String(), iface.DefaultMTU)
|
||||
e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, relayMgr, conf, MobileDependency{}, peer.NewRecorder("https://mgm"), nil, nil), nil
|
||||
e, err := NewEngine(ctx, cancel, conf, EngineServices{
|
||||
SignalClient: signalClient,
|
||||
MgmClient: mgmtClient,
|
||||
RelayManager: relayMgr,
|
||||
StatusRecorder: peer.NewRecorder("https://mgm"),
|
||||
}, MobileDependency{}), nil
|
||||
e.ctx = ctx
|
||||
return e, err
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@ const renewTimeout = 10 * time.Second
|
||||
|
||||
// Response holds the response from exposing a service.
|
||||
type Response struct {
|
||||
ServiceName string
|
||||
ServiceURL string
|
||||
Domain string
|
||||
ServiceName string
|
||||
ServiceURL string
|
||||
Domain string
|
||||
PortAutoAssigned bool
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
@@ -25,6 +26,7 @@ type Request struct {
|
||||
Pin string
|
||||
Password string
|
||||
UserGroups []string
|
||||
ListenPort uint16
|
||||
}
|
||||
|
||||
type ManagementClient interface {
|
||||
|
||||
@@ -15,6 +15,7 @@ func NewRequest(req *daemonProto.ExposeServiceRequest) *Request {
|
||||
UserGroups: req.UserGroups,
|
||||
Domain: req.Domain,
|
||||
NamePrefix: req.NamePrefix,
|
||||
ListenPort: uint16(req.ListenPort),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +28,15 @@ func toClientExposeRequest(req Request) mgm.ExposeRequest {
|
||||
Pin: req.Pin,
|
||||
Password: req.Password,
|
||||
UserGroups: req.UserGroups,
|
||||
ListenPort: req.ListenPort,
|
||||
}
|
||||
}
|
||||
|
||||
func fromClientExposeResponse(response *mgm.ExposeResponse) *Response {
|
||||
return &Response{
|
||||
ServiceName: response.ServiceName,
|
||||
Domain: response.Domain,
|
||||
ServiceURL: response.ServiceURL,
|
||||
ServiceName: response.ServiceName,
|
||||
Domain: response.Domain,
|
||||
ServiceURL: response.ServiceURL,
|
||||
PortAutoAssigned: response.PortAutoAssigned,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func getConfigDirForUser(username string) (string, error) {
|
||||
|
||||
configDir := filepath.Join(DefaultConfigPathDir, username)
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(configDir, 0600); err != nil {
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -206,9 +206,15 @@ func getConfigDirForUser(username string) (string, error) {
|
||||
return configDir, nil
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
func fileExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||
@@ -635,7 +641,11 @@ func isPreSharedKeyHidden(preSharedKey *string) bool {
|
||||
|
||||
// UpdateConfig update existing configuration according to input configuration and return with the configuration
|
||||
func UpdateConfig(input ConfigInput) (*Config, error) {
|
||||
if !fileExists(input.ConfigPath) {
|
||||
configExists, err := fileExists(input.ConfigPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if config file exists: %w", err)
|
||||
}
|
||||
if !configExists {
|
||||
return nil, fmt.Errorf("config file %s does not exist", input.ConfigPath)
|
||||
}
|
||||
|
||||
@@ -644,7 +654,11 @@ func UpdateConfig(input ConfigInput) (*Config, error) {
|
||||
|
||||
// UpdateOrCreateConfig reads existing config or generates a new one
|
||||
func UpdateOrCreateConfig(input ConfigInput) (*Config, error) {
|
||||
if !fileExists(input.ConfigPath) {
|
||||
configExists, err := fileExists(input.ConfigPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if config file exists: %w", err)
|
||||
}
|
||||
if !configExists {
|
||||
log.Infof("generating new config %s", input.ConfigPath)
|
||||
cfg, err := createNewConfig(input)
|
||||
if err != nil {
|
||||
@@ -657,7 +671,7 @@ func UpdateOrCreateConfig(input ConfigInput) (*Config, error) {
|
||||
if isPreSharedKeyHidden(input.PreSharedKey) {
|
||||
input.PreSharedKey = nil
|
||||
}
|
||||
err := util.EnforcePermission(input.ConfigPath)
|
||||
err = util.EnforcePermission(input.ConfigPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to enforce permission on config dir: %v", err)
|
||||
}
|
||||
@@ -784,7 +798,12 @@ func ReadConfig(configPath string) (*Config, error) {
|
||||
|
||||
// ReadConfig read config file and return with Config. If it is not exists create a new with default values
|
||||
func readConfig(configPath string, createIfMissing bool) (*Config, error) {
|
||||
if fileExists(configPath) {
|
||||
configExists, err := fileExists(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if config file exists: %w", err)
|
||||
}
|
||||
|
||||
if configExists {
|
||||
err := util.EnforcePermission(configPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to enforce permission on config dir: %v", err)
|
||||
@@ -831,7 +850,11 @@ func DirectWriteOutConfig(path string, config *Config) error {
|
||||
// DirectUpdateOrCreateConfig is like UpdateOrCreateConfig but uses direct (non-atomic) writes.
|
||||
// Use this on platforms where atomic writes are blocked (e.g., tvOS sandbox).
|
||||
func DirectUpdateOrCreateConfig(input ConfigInput) (*Config, error) {
|
||||
if !fileExists(input.ConfigPath) {
|
||||
configExists, err := fileExists(input.ConfigPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if config file exists: %w", err)
|
||||
}
|
||||
if !configExists {
|
||||
log.Infof("generating new config %s", input.ConfigPath)
|
||||
cfg, err := createNewConfig(input)
|
||||
if err != nil {
|
||||
|
||||
@@ -256,7 +256,11 @@ func (s *ServiceManager) AddProfile(profileName, username string) error {
|
||||
}
|
||||
|
||||
profPath := filepath.Join(configDir, profileName+".json")
|
||||
if fileExists(profPath) {
|
||||
profileExists, err := fileExists(profPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if profile exists: %w", err)
|
||||
}
|
||||
if profileExists {
|
||||
return ErrProfileAlreadyExists
|
||||
}
|
||||
|
||||
@@ -285,7 +289,11 @@ func (s *ServiceManager) RemoveProfile(profileName, username string) error {
|
||||
return fmt.Errorf("cannot remove profile with reserved name: %s", defaultProfileName)
|
||||
}
|
||||
profPath := filepath.Join(configDir, profileName+".json")
|
||||
if !fileExists(profPath) {
|
||||
profileExists, err := fileExists(profPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if profile exists: %w", err)
|
||||
}
|
||||
if !profileExists {
|
||||
return ErrProfileNotFound
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,11 @@ func (pm *ProfileManager) GetProfileState(profileName string) (*ProfileState, er
|
||||
}
|
||||
|
||||
stateFile := filepath.Join(configDir, profileName+".state.json")
|
||||
if !fileExists(stateFile) {
|
||||
stateFileExists, err := fileExists(stateFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if profile state file exists: %w", err)
|
||||
}
|
||||
if !stateFileExists {
|
||||
return nil, errors.New("profile state file does not exist")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ package client
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -263,8 +265,14 @@ func (w *Watcher) watchPeerStatusChanges(ctx context.Context, peerKey string, pe
|
||||
case <-closer:
|
||||
return
|
||||
case routerStates := <-subscription.Events():
|
||||
peerStateUpdate <- routerStates
|
||||
log.Debugf("triggered route state update for Peer: %s", peerKey)
|
||||
select {
|
||||
case peerStateUpdate <- routerStates:
|
||||
log.Debugf("triggered route state update for Peer: %s", peerKey)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-closer:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,7 +566,7 @@ func HandlerFromRoute(params common.HandlerParams) RouteHandler {
|
||||
return dnsinterceptor.New(params)
|
||||
case handlerTypeDynamic:
|
||||
dns := nbdns.NewServiceViaMemory(params.WgInterface)
|
||||
dnsAddr := fmt.Sprintf("%s:%d", dns.RuntimeIP(), dns.RuntimePort())
|
||||
dnsAddr := net.JoinHostPort(dns.RuntimeIP().String(), strconv.Itoa(dns.RuntimePort()))
|
||||
return dynamic.NewRoute(params, dnsAddr)
|
||||
default:
|
||||
return static.NewRoute(params)
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -249,7 +251,7 @@ func (d *DnsInterceptor) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||
r.MsgHdr.AuthenticatedData = true
|
||||
}
|
||||
|
||||
upstream := fmt.Sprintf("%s:%d", upstreamIP.String(), uint16(d.forwarderPort.Load()))
|
||||
upstream := net.JoinHostPort(upstreamIP.String(), strconv.FormatUint(uint64(d.forwarderPort.Load()), 10))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), dnsTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
//go:build windows || darwin
|
||||
|
||||
package updatemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v "github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
)
|
||||
|
||||
type versionUpdateMock struct {
|
||||
latestVersion *v.Version
|
||||
onUpdate func()
|
||||
}
|
||||
|
||||
func (v versionUpdateMock) StopWatch() {}
|
||||
|
||||
func (v versionUpdateMock) SetDaemonVersion(newVersion string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *versionUpdateMock) SetOnUpdateListener(updateFn func()) {
|
||||
v.onUpdate = updateFn
|
||||
}
|
||||
|
||||
func (v versionUpdateMock) LatestVersion() *v.Version {
|
||||
return v.latestVersion
|
||||
}
|
||||
|
||||
func (v versionUpdateMock) StartFetcher() {}
|
||||
|
||||
func Test_LatestVersion(t *testing.T) {
|
||||
testMatrix := []struct {
|
||||
name string
|
||||
daemonVersion string
|
||||
initialLatestVersion *v.Version
|
||||
latestVersion *v.Version
|
||||
shouldUpdateInit bool
|
||||
shouldUpdateLater bool
|
||||
}{
|
||||
{
|
||||
name: "Should only trigger update once due to time between triggers being < 5 Minutes",
|
||||
daemonVersion: "1.0.0",
|
||||
initialLatestVersion: v.Must(v.NewSemver("1.0.1")),
|
||||
latestVersion: v.Must(v.NewSemver("1.0.2")),
|
||||
shouldUpdateInit: true,
|
||||
shouldUpdateLater: false,
|
||||
},
|
||||
{
|
||||
name: "Shouldn't update initially, but should update as soon as latest version is fetched",
|
||||
daemonVersion: "1.0.0",
|
||||
initialLatestVersion: nil,
|
||||
latestVersion: v.Must(v.NewSemver("1.0.1")),
|
||||
shouldUpdateInit: false,
|
||||
shouldUpdateLater: true,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range testMatrix {
|
||||
mockUpdate := &versionUpdateMock{latestVersion: c.initialLatestVersion}
|
||||
tmpFile := path.Join(t.TempDir(), fmt.Sprintf("update-test-%d.json", idx))
|
||||
m, _ := newManager(peer.NewRecorder(""), statemanager.New(tmpFile))
|
||||
m.update = mockUpdate
|
||||
|
||||
targetVersionChan := make(chan string, 1)
|
||||
|
||||
m.triggerUpdateFn = func(ctx context.Context, targetVersion string) error {
|
||||
targetVersionChan <- targetVersion
|
||||
return nil
|
||||
}
|
||||
m.currentVersion = c.daemonVersion
|
||||
m.Start(context.Background())
|
||||
m.SetVersion("latest")
|
||||
var triggeredInit bool
|
||||
select {
|
||||
case targetVersion := <-targetVersionChan:
|
||||
if targetVersion != c.initialLatestVersion.String() {
|
||||
t.Errorf("%s: Initial update version mismatch, expected %v, got %v", c.name, c.initialLatestVersion.String(), targetVersion)
|
||||
}
|
||||
triggeredInit = true
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
triggeredInit = false
|
||||
}
|
||||
if triggeredInit != c.shouldUpdateInit {
|
||||
t.Errorf("%s: Initial update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdateInit, triggeredInit)
|
||||
}
|
||||
|
||||
mockUpdate.latestVersion = c.latestVersion
|
||||
mockUpdate.onUpdate()
|
||||
|
||||
var triggeredLater bool
|
||||
select {
|
||||
case targetVersion := <-targetVersionChan:
|
||||
if targetVersion != c.latestVersion.String() {
|
||||
t.Errorf("%s: Update version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), targetVersion)
|
||||
}
|
||||
triggeredLater = true
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
triggeredLater = false
|
||||
}
|
||||
if triggeredLater != c.shouldUpdateLater {
|
||||
t.Errorf("%s: Update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdateLater, triggeredLater)
|
||||
}
|
||||
|
||||
m.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HandleUpdate(t *testing.T) {
|
||||
testMatrix := []struct {
|
||||
name string
|
||||
daemonVersion string
|
||||
latestVersion *v.Version
|
||||
expectedVersion string
|
||||
shouldUpdate bool
|
||||
}{
|
||||
{
|
||||
name: "Update to a specific version should update regardless of if latestVersion is available yet",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "0.56.0",
|
||||
shouldUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "Update to specific version should not update if version matches",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "0.55.0",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Update to specific version should not update if current version is newer",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "0.54.0",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Update to latest version should update if latest is newer",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: v.Must(v.NewSemver("0.56.0")),
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "Update to latest version should not update if latest == current",
|
||||
daemonVersion: "0.56.0",
|
||||
latestVersion: v.Must(v.NewSemver("0.56.0")),
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Should not update if daemon version is invalid",
|
||||
daemonVersion: "development",
|
||||
latestVersion: v.Must(v.NewSemver("1.0.0")),
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Should not update if expecting latest and latest version is unavailable",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Should not update if expected version is invalid",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "development",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
}
|
||||
for idx, c := range testMatrix {
|
||||
tmpFile := path.Join(t.TempDir(), fmt.Sprintf("update-test-%d.json", idx))
|
||||
m, _ := newManager(peer.NewRecorder(""), statemanager.New(tmpFile))
|
||||
m.update = &versionUpdateMock{latestVersion: c.latestVersion}
|
||||
targetVersionChan := make(chan string, 1)
|
||||
|
||||
m.triggerUpdateFn = func(ctx context.Context, targetVersion string) error {
|
||||
targetVersionChan <- targetVersion
|
||||
return nil
|
||||
}
|
||||
|
||||
m.currentVersion = c.daemonVersion
|
||||
m.Start(context.Background())
|
||||
m.SetVersion(c.expectedVersion)
|
||||
|
||||
var updateTriggered bool
|
||||
select {
|
||||
case targetVersion := <-targetVersionChan:
|
||||
if c.expectedVersion == "latest" && targetVersion != c.latestVersion.String() {
|
||||
t.Errorf("%s: Update version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), targetVersion)
|
||||
} else if c.expectedVersion != "latest" && targetVersion != c.expectedVersion {
|
||||
t.Errorf("%s: Update version mismatch, expected %v, got %v", c.name, c.expectedVersion, targetVersion)
|
||||
}
|
||||
updateTriggered = true
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
updateTriggered = false
|
||||
}
|
||||
|
||||
if updateTriggered != c.shouldUpdate {
|
||||
t.Errorf("%s: Update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdate, updateTriggered)
|
||||
}
|
||||
m.Stop()
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
//go:build !windows && !darwin
|
||||
|
||||
package updatemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
)
|
||||
|
||||
// Manager is a no-op stub for unsupported platforms
|
||||
type Manager struct{}
|
||||
|
||||
// NewManager returns a no-op manager for unsupported platforms
|
||||
func NewManager(statusRecorder *peer.Status, stateManager *statemanager.Manager) (*Manager, error) {
|
||||
return nil, fmt.Errorf("update manager is not supported on this platform")
|
||||
}
|
||||
|
||||
// CheckUpdateSuccess is a no-op on unsupported platforms
|
||||
func (m *Manager) CheckUpdateSuccess(ctx context.Context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// Start is a no-op on unsupported platforms
|
||||
func (m *Manager) Start(ctx context.Context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// SetVersion is a no-op on unsupported platforms
|
||||
func (m *Manager) SetVersion(expectedVersion string) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// Stop is a no-op on unsupported platforms
|
||||
func (m *Manager) Stop() {
|
||||
// no-op
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package updatemanager provides automatic update management for the NetBird client.
|
||||
// Package updater provides automatic update management for the NetBird client.
|
||||
// It monitors for new versions, handles update triggers from management server directives,
|
||||
// and orchestrates the download and installation of client updates.
|
||||
//
|
||||
@@ -32,4 +32,4 @@
|
||||
//
|
||||
// This enables verification of successful updates and appropriate user notification
|
||||
// after the client restarts with the new version.
|
||||
package updatemanager
|
||||
package updater
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
goversion "github.com/hashicorp/go-version"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/downloader"
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/reposign"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/downloader"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/reposign"
|
||||
)
|
||||
|
||||
type Installer struct {
|
||||
@@ -203,7 +203,10 @@ func (rh *ResultHandler) write(result Result) error {
|
||||
|
||||
func (rh *ResultHandler) cleanup() error {
|
||||
err := os.Remove(rh.resultFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.Debugf("delete installer result file: %s", rh.resultFile)
|
||||
@@ -1,12 +1,9 @@
|
||||
//go:build windows || darwin
|
||||
|
||||
package updatemanager
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -15,7 +12,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/installer"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/installer"
|
||||
cProto "github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
@@ -41,6 +38,9 @@ type Manager struct {
|
||||
statusRecorder *peer.Status
|
||||
stateManager *statemanager.Manager
|
||||
|
||||
downloadOnly bool // true when no enforcement from management; notifies UI to download latest
|
||||
forceUpdate bool // true when management sets AlwaysUpdate; skips UI interaction and installs directly
|
||||
|
||||
lastTrigger time.Time
|
||||
mgmUpdateChan chan struct{}
|
||||
updateChannel chan struct{}
|
||||
@@ -53,37 +53,38 @@ type Manager struct {
|
||||
expectedVersion *v.Version
|
||||
updateToLatestVersion bool
|
||||
|
||||
// updateMutex protect update and expectedVersion fields
|
||||
pendingVersion *v.Version
|
||||
|
||||
// updateMutex protects update, expectedVersion, updateToLatestVersion,
|
||||
// downloadOnly, forceUpdate, pendingVersion, and lastTrigger fields
|
||||
updateMutex sync.Mutex
|
||||
|
||||
triggerUpdateFn func(context.Context, string) error
|
||||
// installMutex and installing guard against concurrent installation attempts
|
||||
installMutex sync.Mutex
|
||||
installing bool
|
||||
|
||||
// protect to start the service multiple times
|
||||
mu sync.Mutex
|
||||
|
||||
autoUpdateSupported func() bool
|
||||
}
|
||||
|
||||
func NewManager(statusRecorder *peer.Status, stateManager *statemanager.Manager) (*Manager, error) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
isBrew := !installer.TypeOfInstaller(context.Background()).Downloadable()
|
||||
if isBrew {
|
||||
log.Warnf("auto-update disabled on Home Brew installation")
|
||||
return nil, fmt.Errorf("auto-update not supported on Home Brew installation yet")
|
||||
}
|
||||
}
|
||||
return newManager(statusRecorder, stateManager)
|
||||
}
|
||||
|
||||
func newManager(statusRecorder *peer.Status, stateManager *statemanager.Manager) (*Manager, error) {
|
||||
// NewManager creates a new update manager. The manager is single-use: once Stop() is called, it cannot be restarted.
|
||||
func NewManager(statusRecorder *peer.Status, stateManager *statemanager.Manager) *Manager {
|
||||
manager := &Manager{
|
||||
statusRecorder: statusRecorder,
|
||||
stateManager: stateManager,
|
||||
mgmUpdateChan: make(chan struct{}, 1),
|
||||
updateChannel: make(chan struct{}, 1),
|
||||
currentVersion: version.NetbirdVersion(),
|
||||
update: version.NewUpdate("nb/client"),
|
||||
statusRecorder: statusRecorder,
|
||||
stateManager: stateManager,
|
||||
mgmUpdateChan: make(chan struct{}, 1),
|
||||
updateChannel: make(chan struct{}, 1),
|
||||
currentVersion: version.NetbirdVersion(),
|
||||
update: version.NewUpdate("nb/client"),
|
||||
downloadOnly: true,
|
||||
autoUpdateSupported: isAutoUpdateSupported,
|
||||
}
|
||||
manager.triggerUpdateFn = manager.triggerUpdate
|
||||
|
||||
stateManager.RegisterState(&UpdateState{})
|
||||
|
||||
return manager, nil
|
||||
return manager
|
||||
}
|
||||
|
||||
// CheckUpdateSuccess checks if the update was successful and send a notification.
|
||||
@@ -124,8 +125,10 @@ func (m *Manager) CheckUpdateSuccess(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (m *Manager) Start(ctx context.Context) {
|
||||
log.Infof("starting update manager")
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.cancel != nil {
|
||||
log.Errorf("Manager already started")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -142,13 +145,32 @@ func (m *Manager) Start(ctx context.Context) {
|
||||
m.cancel = cancel
|
||||
|
||||
m.wg.Add(1)
|
||||
go m.updateLoop(ctx)
|
||||
go func() {
|
||||
defer m.wg.Done()
|
||||
m.updateLoop(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *Manager) SetVersion(expectedVersion string) {
|
||||
log.Infof("set expected agent version for upgrade: %s", expectedVersion)
|
||||
if m.cancel == nil {
|
||||
log.Errorf("manager not started")
|
||||
func (m *Manager) SetDownloadOnly() {
|
||||
m.updateMutex.Lock()
|
||||
m.downloadOnly = true
|
||||
m.forceUpdate = false
|
||||
m.expectedVersion = nil
|
||||
m.updateToLatestVersion = false
|
||||
m.lastTrigger = time.Time{}
|
||||
m.updateMutex.Unlock()
|
||||
|
||||
select {
|
||||
case m.mgmUpdateChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) SetVersion(expectedVersion string, forceUpdate bool) {
|
||||
log.Infof("expected version changed to %s, force update: %t", expectedVersion, forceUpdate)
|
||||
|
||||
if !m.autoUpdateSupported() {
|
||||
log.Warnf("auto-update not supported on this platform")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -159,6 +181,7 @@ func (m *Manager) SetVersion(expectedVersion string) {
|
||||
log.Errorf("empty expected version provided")
|
||||
m.expectedVersion = nil
|
||||
m.updateToLatestVersion = false
|
||||
m.downloadOnly = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,12 +201,97 @@ func (m *Manager) SetVersion(expectedVersion string) {
|
||||
m.updateToLatestVersion = false
|
||||
}
|
||||
|
||||
m.lastTrigger = time.Time{}
|
||||
m.downloadOnly = false
|
||||
m.forceUpdate = forceUpdate
|
||||
|
||||
select {
|
||||
case m.mgmUpdateChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Install triggers the installation of the pending version. It is called when the user clicks the install button in the UI.
|
||||
func (m *Manager) Install(ctx context.Context) error {
|
||||
if !m.autoUpdateSupported() {
|
||||
return fmt.Errorf("auto-update not supported on this platform")
|
||||
}
|
||||
|
||||
m.updateMutex.Lock()
|
||||
pending := m.pendingVersion
|
||||
m.updateMutex.Unlock()
|
||||
|
||||
if pending == nil {
|
||||
return fmt.Errorf("no pending version to install")
|
||||
}
|
||||
|
||||
return m.tryInstall(ctx, pending)
|
||||
}
|
||||
|
||||
// tryInstall ensures only one installation runs at a time. Concurrent callers
|
||||
// receive an error immediately rather than queuing behind a running install.
|
||||
func (m *Manager) tryInstall(ctx context.Context, targetVersion *v.Version) error {
|
||||
m.installMutex.Lock()
|
||||
if m.installing {
|
||||
m.installMutex.Unlock()
|
||||
return fmt.Errorf("installation already in progress")
|
||||
}
|
||||
m.installing = true
|
||||
m.installMutex.Unlock()
|
||||
|
||||
defer func() {
|
||||
m.installMutex.Lock()
|
||||
m.installing = false
|
||||
m.installMutex.Unlock()
|
||||
}()
|
||||
|
||||
return m.install(ctx, targetVersion)
|
||||
}
|
||||
|
||||
// NotifyUI re-publishes the current update state to a newly connected UI client.
|
||||
// Only needed for download-only mode where the latest version is already cached
|
||||
// NotifyUI re-publishes the current update state so a newly connected UI gets the info.
|
||||
func (m *Manager) NotifyUI() {
|
||||
m.updateMutex.Lock()
|
||||
if m.update == nil {
|
||||
m.updateMutex.Unlock()
|
||||
return
|
||||
}
|
||||
downloadOnly := m.downloadOnly
|
||||
pendingVersion := m.pendingVersion
|
||||
latestVersion := m.update.LatestVersion()
|
||||
m.updateMutex.Unlock()
|
||||
|
||||
if downloadOnly {
|
||||
if latestVersion == nil {
|
||||
return
|
||||
}
|
||||
currentVersion, err := v.NewVersion(m.currentVersion)
|
||||
if err != nil || currentVersion.GreaterThanOrEqual(latestVersion) {
|
||||
return
|
||||
}
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_INFO,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
"New version available",
|
||||
"",
|
||||
map[string]string{"new_version_available": latestVersion.String()},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if pendingVersion != nil {
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_INFO,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
"New version available",
|
||||
"",
|
||||
map[string]string{"new_version_available": pendingVersion.String(), "enforced": "true"},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop is not used at the moment because it fully depends on the daemon. In a future refactor it may make sense to use it.
|
||||
func (m *Manager) Stop() {
|
||||
if m.cancel == nil {
|
||||
return
|
||||
@@ -214,8 +322,6 @@ func (m *Manager) onContextCancel() {
|
||||
}
|
||||
|
||||
func (m *Manager) updateLoop(ctx context.Context) {
|
||||
defer m.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -239,55 +345,89 @@ func (m *Manager) handleUpdate(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
expectedVersion := m.expectedVersion
|
||||
useLatest := m.updateToLatestVersion
|
||||
downloadOnly := m.downloadOnly
|
||||
forceUpdate := m.forceUpdate
|
||||
curLatestVersion := m.update.LatestVersion()
|
||||
m.updateMutex.Unlock()
|
||||
|
||||
switch {
|
||||
// Resolve "latest" to actual version
|
||||
case useLatest:
|
||||
// Download-only mode or resolve "latest" to actual version
|
||||
case downloadOnly, m.updateToLatestVersion:
|
||||
if curLatestVersion == nil {
|
||||
log.Tracef("latest version not fetched yet")
|
||||
m.updateMutex.Unlock()
|
||||
return
|
||||
}
|
||||
updateVersion = curLatestVersion
|
||||
// Update to specific version
|
||||
case expectedVersion != nil:
|
||||
updateVersion = expectedVersion
|
||||
// Install to specific version
|
||||
case m.expectedVersion != nil:
|
||||
updateVersion = m.expectedVersion
|
||||
default:
|
||||
log.Debugf("no expected version information set")
|
||||
m.updateMutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("checking update option, current version: %s, target version: %s", m.currentVersion, updateVersion)
|
||||
if !m.shouldUpdate(updateVersion) {
|
||||
if !m.shouldUpdate(updateVersion, forceUpdate) {
|
||||
m.updateMutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
m.lastTrigger = time.Now()
|
||||
log.Infof("Auto-update triggered, current version: %s, target version: %s", m.currentVersion, updateVersion)
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_CRITICAL,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
"Automatically updating client",
|
||||
"Your client version is older than auto-update version set in Management, updating client now.",
|
||||
nil,
|
||||
)
|
||||
log.Infof("new version available: %s", updateVersion)
|
||||
|
||||
if !downloadOnly && !forceUpdate {
|
||||
m.pendingVersion = updateVersion
|
||||
}
|
||||
m.updateMutex.Unlock()
|
||||
|
||||
if downloadOnly {
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_INFO,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
"New version available",
|
||||
"",
|
||||
map[string]string{"new_version_available": updateVersion.String()},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if forceUpdate {
|
||||
if err := m.tryInstall(ctx, updateVersion); err != nil {
|
||||
log.Errorf("force update failed: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_INFO,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
"New version available",
|
||||
"",
|
||||
map[string]string{"new_version_available": updateVersion.String(), "enforced": "true"},
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Manager) install(ctx context.Context, pendingVersion *v.Version) error {
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_CRITICAL,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
"Updating client",
|
||||
"Installing update now.",
|
||||
nil,
|
||||
)
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_CRITICAL,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
"",
|
||||
"",
|
||||
map[string]string{"progress_window": "show", "version": updateVersion.String()},
|
||||
map[string]string{"progress_window": "show", "version": pendingVersion.String()},
|
||||
)
|
||||
|
||||
updateState := UpdateState{
|
||||
PreUpdateVersion: m.currentVersion,
|
||||
TargetVersion: updateVersion.String(),
|
||||
TargetVersion: pendingVersion.String(),
|
||||
}
|
||||
|
||||
if err := m.stateManager.UpdateState(updateState); err != nil {
|
||||
log.Warnf("failed to update state: %v", err)
|
||||
} else {
|
||||
@@ -296,8 +436,9 @@ func (m *Manager) handleUpdate(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.triggerUpdateFn(ctx, updateVersion.String()); err != nil {
|
||||
log.Errorf("Error triggering auto-update: %v", err)
|
||||
inst := installer.New()
|
||||
if err := inst.RunInstallation(ctx, pendingVersion.String()); err != nil {
|
||||
log.Errorf("error triggering update: %v", err)
|
||||
m.statusRecorder.PublishEvent(
|
||||
cProto.SystemEvent_ERROR,
|
||||
cProto.SystemEvent_SYSTEM,
|
||||
@@ -305,7 +446,9 @@ func (m *Manager) handleUpdate(ctx context.Context) {
|
||||
fmt.Sprintf("Auto-update failed: %v", err),
|
||||
nil,
|
||||
)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadAndDeleteUpdateState loads the update state, deletes it from storage, and returns it.
|
||||
@@ -339,7 +482,7 @@ func (m *Manager) loadAndDeleteUpdateState(ctx context.Context) (*UpdateState, e
|
||||
return updateState, nil
|
||||
}
|
||||
|
||||
func (m *Manager) shouldUpdate(updateVersion *v.Version) bool {
|
||||
func (m *Manager) shouldUpdate(updateVersion *v.Version, forceUpdate bool) bool {
|
||||
if m.currentVersion == developmentVersion {
|
||||
log.Debugf("skipping auto-update, running development version")
|
||||
return false
|
||||
@@ -354,8 +497,8 @@ func (m *Manager) shouldUpdate(updateVersion *v.Version) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if time.Since(m.lastTrigger) < 5*time.Minute {
|
||||
log.Debugf("skipping auto-update, last update was %s ago", time.Since(m.lastTrigger))
|
||||
if forceUpdate && time.Since(m.lastTrigger) < 3*time.Minute {
|
||||
log.Infof("skipping auto-update, last update was %s ago", time.Since(m.lastTrigger))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -367,8 +510,3 @@ func (m *Manager) lastResultErrReason() string {
|
||||
result := installer.NewResultHandler(inst.TempDir())
|
||||
return result.GetErrorResultReason()
|
||||
}
|
||||
|
||||
func (m *Manager) triggerUpdate(ctx context.Context, targetVersion string) error {
|
||||
inst := installer.New()
|
||||
return inst.RunInstallation(ctx, targetVersion)
|
||||
}
|
||||
111
client/internal/updater/manager_linux_test.go
Normal file
111
client/internal/updater/manager_linux_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
//go:build !windows && !darwin
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v "github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
)
|
||||
|
||||
// On Linux, only Mode 1 (downloadOnly) is supported.
|
||||
// SetVersion is a no-op because auto-update installation is not supported.
|
||||
|
||||
func Test_LatestVersion_Linux(t *testing.T) {
|
||||
testMatrix := []struct {
|
||||
name string
|
||||
daemonVersion string
|
||||
initialLatestVersion *v.Version
|
||||
latestVersion *v.Version
|
||||
shouldUpdateInit bool
|
||||
shouldUpdateLater bool
|
||||
}{
|
||||
{
|
||||
name: "Should notify again when a newer version arrives even within 5 minutes",
|
||||
daemonVersion: "1.0.0",
|
||||
initialLatestVersion: v.Must(v.NewSemver("1.0.1")),
|
||||
latestVersion: v.Must(v.NewSemver("1.0.2")),
|
||||
shouldUpdateInit: true,
|
||||
shouldUpdateLater: true,
|
||||
},
|
||||
{
|
||||
name: "Shouldn't notify initially, but should notify as soon as latest version is fetched",
|
||||
daemonVersion: "1.0.0",
|
||||
initialLatestVersion: nil,
|
||||
latestVersion: v.Must(v.NewSemver("1.0.1")),
|
||||
shouldUpdateInit: false,
|
||||
shouldUpdateLater: true,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range testMatrix {
|
||||
mockUpdate := &versionUpdateMock{latestVersion: c.initialLatestVersion}
|
||||
tmpFile := path.Join(t.TempDir(), fmt.Sprintf("update-test-%d.json", idx))
|
||||
recorder := peer.NewRecorder("")
|
||||
sub := recorder.SubscribeToEvents()
|
||||
defer recorder.UnsubscribeFromEvents(sub)
|
||||
|
||||
m := NewManager(recorder, statemanager.New(tmpFile))
|
||||
m.update = mockUpdate
|
||||
m.currentVersion = c.daemonVersion
|
||||
m.Start(context.Background())
|
||||
m.SetDownloadOnly()
|
||||
|
||||
ver, enforced := waitForUpdateEvent(sub, 500*time.Millisecond)
|
||||
triggeredInit := ver != ""
|
||||
if enforced {
|
||||
t.Errorf("%s: Linux Mode 1 must never have enforced metadata", c.name)
|
||||
}
|
||||
if triggeredInit != c.shouldUpdateInit {
|
||||
t.Errorf("%s: Initial notify mismatch, expected %v, got %v", c.name, c.shouldUpdateInit, triggeredInit)
|
||||
}
|
||||
if triggeredInit && c.initialLatestVersion != nil && ver != c.initialLatestVersion.String() {
|
||||
t.Errorf("%s: Initial version mismatch, expected %v, got %v", c.name, c.initialLatestVersion.String(), ver)
|
||||
}
|
||||
|
||||
mockUpdate.latestVersion = c.latestVersion
|
||||
mockUpdate.onUpdate()
|
||||
|
||||
ver, enforced = waitForUpdateEvent(sub, 500*time.Millisecond)
|
||||
triggeredLater := ver != ""
|
||||
if enforced {
|
||||
t.Errorf("%s: Linux Mode 1 must never have enforced metadata", c.name)
|
||||
}
|
||||
if triggeredLater != c.shouldUpdateLater {
|
||||
t.Errorf("%s: Later notify mismatch, expected %v, got %v", c.name, c.shouldUpdateLater, triggeredLater)
|
||||
}
|
||||
if triggeredLater && c.latestVersion != nil && ver != c.latestVersion.String() {
|
||||
t.Errorf("%s: Later version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), ver)
|
||||
}
|
||||
|
||||
m.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SetVersion_NoOp_Linux(t *testing.T) {
|
||||
// On Linux, SetVersion should be a no-op — no events fired
|
||||
tmpFile := path.Join(t.TempDir(), "update-test-noop.json")
|
||||
recorder := peer.NewRecorder("")
|
||||
sub := recorder.SubscribeToEvents()
|
||||
defer recorder.UnsubscribeFromEvents(sub)
|
||||
|
||||
m := NewManager(recorder, statemanager.New(tmpFile))
|
||||
m.update = &versionUpdateMock{latestVersion: v.Must(v.NewSemver("1.0.1"))}
|
||||
m.currentVersion = "1.0.0"
|
||||
m.Start(context.Background())
|
||||
m.SetVersion("1.0.1", false)
|
||||
|
||||
ver, _ := waitForUpdateEvent(sub, 500*time.Millisecond)
|
||||
if ver != "" {
|
||||
t.Errorf("SetVersion should be a no-op on Linux, but got event with version %s", ver)
|
||||
}
|
||||
|
||||
m.Stop()
|
||||
}
|
||||
227
client/internal/updater/manager_test.go
Normal file
227
client/internal/updater/manager_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
//go:build windows || darwin
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v "github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
cProto "github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
func Test_LatestVersion(t *testing.T) {
|
||||
testMatrix := []struct {
|
||||
name string
|
||||
daemonVersion string
|
||||
initialLatestVersion *v.Version
|
||||
latestVersion *v.Version
|
||||
shouldUpdateInit bool
|
||||
shouldUpdateLater bool
|
||||
}{
|
||||
{
|
||||
name: "Should notify again when a newer version arrives even within 5 minutes",
|
||||
daemonVersion: "1.0.0",
|
||||
initialLatestVersion: v.Must(v.NewSemver("1.0.1")),
|
||||
latestVersion: v.Must(v.NewSemver("1.0.2")),
|
||||
shouldUpdateInit: true,
|
||||
shouldUpdateLater: true,
|
||||
},
|
||||
{
|
||||
name: "Shouldn't update initially, but should update as soon as latest version is fetched",
|
||||
daemonVersion: "1.0.0",
|
||||
initialLatestVersion: nil,
|
||||
latestVersion: v.Must(v.NewSemver("1.0.1")),
|
||||
shouldUpdateInit: false,
|
||||
shouldUpdateLater: true,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, c := range testMatrix {
|
||||
mockUpdate := &versionUpdateMock{latestVersion: c.initialLatestVersion}
|
||||
tmpFile := path.Join(t.TempDir(), fmt.Sprintf("update-test-%d.json", idx))
|
||||
recorder := peer.NewRecorder("")
|
||||
sub := recorder.SubscribeToEvents()
|
||||
defer recorder.UnsubscribeFromEvents(sub)
|
||||
|
||||
m := NewManager(recorder, statemanager.New(tmpFile))
|
||||
m.update = mockUpdate
|
||||
m.currentVersion = c.daemonVersion
|
||||
m.autoUpdateSupported = func() bool { return true }
|
||||
m.Start(context.Background())
|
||||
m.SetVersion("latest", false)
|
||||
|
||||
ver, _ := waitForUpdateEvent(sub, 500*time.Millisecond)
|
||||
triggeredInit := ver != ""
|
||||
if triggeredInit != c.shouldUpdateInit {
|
||||
t.Errorf("%s: Initial update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdateInit, triggeredInit)
|
||||
}
|
||||
if triggeredInit && c.initialLatestVersion != nil && ver != c.initialLatestVersion.String() {
|
||||
t.Errorf("%s: Initial update version mismatch, expected %v, got %v", c.name, c.initialLatestVersion.String(), ver)
|
||||
}
|
||||
|
||||
mockUpdate.latestVersion = c.latestVersion
|
||||
mockUpdate.onUpdate()
|
||||
|
||||
ver, _ = waitForUpdateEvent(sub, 500*time.Millisecond)
|
||||
triggeredLater := ver != ""
|
||||
if triggeredLater != c.shouldUpdateLater {
|
||||
t.Errorf("%s: Later update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdateLater, triggeredLater)
|
||||
}
|
||||
if triggeredLater && c.latestVersion != nil && ver != c.latestVersion.String() {
|
||||
t.Errorf("%s: Later update version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), ver)
|
||||
}
|
||||
|
||||
m.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HandleUpdate(t *testing.T) {
|
||||
testMatrix := []struct {
|
||||
name string
|
||||
daemonVersion string
|
||||
latestVersion *v.Version
|
||||
expectedVersion string
|
||||
shouldUpdate bool
|
||||
}{
|
||||
{
|
||||
name: "Install to a specific version should update regardless of if latestVersion is available yet",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "0.56.0",
|
||||
shouldUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "Install to specific version should not update if version matches",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "0.55.0",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Install to specific version should not update if current version is newer",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "0.54.0",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Install to latest version should update if latest is newer",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: v.Must(v.NewSemver("0.56.0")),
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "Install to latest version should not update if latest == current",
|
||||
daemonVersion: "0.56.0",
|
||||
latestVersion: v.Must(v.NewSemver("0.56.0")),
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Should not update if daemon version is invalid",
|
||||
daemonVersion: "development",
|
||||
latestVersion: v.Must(v.NewSemver("1.0.0")),
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Should not update if expecting latest and latest version is unavailable",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "latest",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Should not update if expected version is invalid",
|
||||
daemonVersion: "0.55.0",
|
||||
latestVersion: nil,
|
||||
expectedVersion: "development",
|
||||
shouldUpdate: false,
|
||||
},
|
||||
}
|
||||
for idx, c := range testMatrix {
|
||||
tmpFile := path.Join(t.TempDir(), fmt.Sprintf("update-test-%d.json", idx))
|
||||
recorder := peer.NewRecorder("")
|
||||
sub := recorder.SubscribeToEvents()
|
||||
defer recorder.UnsubscribeFromEvents(sub)
|
||||
|
||||
m := NewManager(recorder, statemanager.New(tmpFile))
|
||||
m.update = &versionUpdateMock{latestVersion: c.latestVersion}
|
||||
m.currentVersion = c.daemonVersion
|
||||
m.autoUpdateSupported = func() bool { return true }
|
||||
m.Start(context.Background())
|
||||
m.SetVersion(c.expectedVersion, false)
|
||||
|
||||
ver, _ := waitForUpdateEvent(sub, 500*time.Millisecond)
|
||||
updateTriggered := ver != ""
|
||||
|
||||
if updateTriggered {
|
||||
if c.expectedVersion == "latest" && c.latestVersion != nil && ver != c.latestVersion.String() {
|
||||
t.Errorf("%s: Version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), ver)
|
||||
} else if c.expectedVersion != "latest" && c.expectedVersion != "development" && ver != c.expectedVersion {
|
||||
t.Errorf("%s: Version mismatch, expected %v, got %v", c.name, c.expectedVersion, ver)
|
||||
}
|
||||
}
|
||||
|
||||
if updateTriggered != c.shouldUpdate {
|
||||
t.Errorf("%s: Update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdate, updateTriggered)
|
||||
}
|
||||
m.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func Test_EnforcedMetadata(t *testing.T) {
|
||||
// Mode 1 (downloadOnly): no enforced metadata
|
||||
tmpFile := path.Join(t.TempDir(), "update-test-mode1.json")
|
||||
recorder := peer.NewRecorder("")
|
||||
sub := recorder.SubscribeToEvents()
|
||||
defer recorder.UnsubscribeFromEvents(sub)
|
||||
|
||||
m := NewManager(recorder, statemanager.New(tmpFile))
|
||||
m.update = &versionUpdateMock{latestVersion: v.Must(v.NewSemver("1.0.1"))}
|
||||
m.currentVersion = "1.0.0"
|
||||
m.Start(context.Background())
|
||||
m.SetDownloadOnly()
|
||||
|
||||
ver, enforced := waitForUpdateEvent(sub, 500*time.Millisecond)
|
||||
if ver == "" {
|
||||
t.Fatal("Mode 1: expected new_version_available event")
|
||||
}
|
||||
if enforced {
|
||||
t.Error("Mode 1: expected no enforced metadata")
|
||||
}
|
||||
m.Stop()
|
||||
|
||||
// Mode 2 (enforced, forceUpdate=false): enforced metadata present, no auto-install
|
||||
tmpFile2 := path.Join(t.TempDir(), "update-test-mode2.json")
|
||||
recorder2 := peer.NewRecorder("")
|
||||
sub2 := recorder2.SubscribeToEvents()
|
||||
defer recorder2.UnsubscribeFromEvents(sub2)
|
||||
|
||||
m2 := NewManager(recorder2, statemanager.New(tmpFile2))
|
||||
m2.update = &versionUpdateMock{latestVersion: nil}
|
||||
m2.currentVersion = "1.0.0"
|
||||
m2.autoUpdateSupported = func() bool { return true }
|
||||
m2.Start(context.Background())
|
||||
m2.SetVersion("1.0.1", false)
|
||||
|
||||
ver, enforced2 := waitForUpdateEvent(sub2, 500*time.Millisecond)
|
||||
if ver == "" {
|
||||
t.Fatal("Mode 2: expected new_version_available event")
|
||||
}
|
||||
if !enforced2 {
|
||||
t.Error("Mode 2: expected enforced metadata")
|
||||
}
|
||||
m2.Stop()
|
||||
}
|
||||
|
||||
// ensure the proto import is used
|
||||
var _ = cProto.SystemEvent_INFO
|
||||
56
client/internal/updater/manager_test_helpers_test.go
Normal file
56
client/internal/updater/manager_test_helpers_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
v "github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
)
|
||||
|
||||
type versionUpdateMock struct {
|
||||
latestVersion *v.Version
|
||||
onUpdate func()
|
||||
}
|
||||
|
||||
func (m versionUpdateMock) StopWatch() {}
|
||||
|
||||
func (m versionUpdateMock) SetDaemonVersion(newVersion string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *versionUpdateMock) SetOnUpdateListener(updateFn func()) {
|
||||
m.onUpdate = updateFn
|
||||
}
|
||||
|
||||
func (m versionUpdateMock) LatestVersion() *v.Version {
|
||||
return m.latestVersion
|
||||
}
|
||||
|
||||
func (m versionUpdateMock) StartFetcher() {}
|
||||
|
||||
// waitForUpdateEvent waits for a new_version_available event, returns the version string or "" on timeout.
|
||||
func waitForUpdateEvent(sub *peer.EventSubscription, timeout time.Duration) (version string, enforced bool) {
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-sub.Events():
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if val, ok := event.Metadata["new_version_available"]; ok {
|
||||
enforced := false
|
||||
if raw, ok := event.Metadata["enforced"]; ok {
|
||||
if parsed, err := strconv.ParseBool(raw); err == nil {
|
||||
enforced = parsed
|
||||
}
|
||||
}
|
||||
return val, enforced
|
||||
}
|
||||
case <-timer.C:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/downloader"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/downloader"
|
||||
)
|
||||
|
||||
const (
|
||||
22
client/internal/updater/supported_darwin.go
Normal file
22
client/internal/updater/supported_darwin.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updater/installer"
|
||||
)
|
||||
|
||||
func isAutoUpdateSupported() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
isBrew := !installer.TypeOfInstaller(ctx).Downloadable()
|
||||
if isBrew {
|
||||
log.Warnf("auto-update disabled on Homebrew installation")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
7
client/internal/updater/supported_other.go
Normal file
7
client/internal/updater/supported_other.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !windows && !darwin
|
||||
|
||||
package updater
|
||||
|
||||
func isAutoUpdateSupported() bool {
|
||||
return false
|
||||
}
|
||||
5
client/internal/updater/supported_windows.go
Normal file
5
client/internal/updater/supported_windows.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package updater
|
||||
|
||||
func isAutoUpdateSupported() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package updatemanager
|
||||
package updater
|
||||
|
||||
import v "github.com/hashicorp/go-version"
|
||||
|
||||
@@ -160,7 +160,7 @@ func (c *Client) Run(fd int32, interfaceName string, envList *EnvList) error {
|
||||
c.onHostDnsFn = func([]string) {}
|
||||
cfg.WgIface = interfaceName
|
||||
|
||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder, false)
|
||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v6.33.3
|
||||
// protoc v6.33.1
|
||||
// source: daemon.proto
|
||||
|
||||
package proto
|
||||
@@ -95,6 +95,7 @@ const (
|
||||
ExposeProtocol_EXPOSE_HTTPS ExposeProtocol = 1
|
||||
ExposeProtocol_EXPOSE_TCP ExposeProtocol = 2
|
||||
ExposeProtocol_EXPOSE_UDP ExposeProtocol = 3
|
||||
ExposeProtocol_EXPOSE_TLS ExposeProtocol = 4
|
||||
)
|
||||
|
||||
// Enum value maps for ExposeProtocol.
|
||||
@@ -104,12 +105,14 @@ var (
|
||||
1: "EXPOSE_HTTPS",
|
||||
2: "EXPOSE_TCP",
|
||||
3: "EXPOSE_UDP",
|
||||
4: "EXPOSE_TLS",
|
||||
}
|
||||
ExposeProtocol_value = map[string]int32{
|
||||
"EXPOSE_HTTP": 0,
|
||||
"EXPOSE_HTTPS": 1,
|
||||
"EXPOSE_TCP": 2,
|
||||
"EXPOSE_UDP": 3,
|
||||
"EXPOSE_TLS": 4,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -945,7 +948,6 @@ type UpRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ProfileName *string `protobuf:"bytes,1,opt,name=profileName,proto3,oneof" json:"profileName,omitempty"`
|
||||
Username *string `protobuf:"bytes,2,opt,name=username,proto3,oneof" json:"username,omitempty"`
|
||||
AutoUpdate *bool `protobuf:"varint,3,opt,name=autoUpdate,proto3,oneof" json:"autoUpdate,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -994,13 +996,6 @@ func (x *UpRequest) GetUsername() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UpRequest) GetAutoUpdate() bool {
|
||||
if x != nil && x.AutoUpdate != nil {
|
||||
return *x.AutoUpdate
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type UpResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
@@ -5032,6 +5027,94 @@ func (x *GetFeaturesResponse) GetDisableUpdateSettings() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type TriggerUpdateRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TriggerUpdateRequest) Reset() {
|
||||
*x = TriggerUpdateRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[73]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *TriggerUpdateRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TriggerUpdateRequest) ProtoMessage() {}
|
||||
|
||||
func (x *TriggerUpdateRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[73]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TriggerUpdateRequest.ProtoReflect.Descriptor instead.
|
||||
func (*TriggerUpdateRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{73}
|
||||
}
|
||||
|
||||
type TriggerUpdateResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
|
||||
ErrorMsg string `protobuf:"bytes,2,opt,name=errorMsg,proto3" json:"errorMsg,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TriggerUpdateResponse) Reset() {
|
||||
*x = TriggerUpdateResponse{}
|
||||
mi := &file_daemon_proto_msgTypes[74]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *TriggerUpdateResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TriggerUpdateResponse) ProtoMessage() {}
|
||||
|
||||
func (x *TriggerUpdateResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[74]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TriggerUpdateResponse.ProtoReflect.Descriptor instead.
|
||||
func (*TriggerUpdateResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{74}
|
||||
}
|
||||
|
||||
func (x *TriggerUpdateResponse) GetSuccess() bool {
|
||||
if x != nil {
|
||||
return x.Success
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *TriggerUpdateResponse) GetErrorMsg() string {
|
||||
if x != nil {
|
||||
return x.ErrorMsg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetPeerSSHHostKeyRequest for retrieving SSH host key for a specific peer
|
||||
type GetPeerSSHHostKeyRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@@ -5043,7 +5126,7 @@ type GetPeerSSHHostKeyRequest struct {
|
||||
|
||||
func (x *GetPeerSSHHostKeyRequest) Reset() {
|
||||
*x = GetPeerSSHHostKeyRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[73]
|
||||
mi := &file_daemon_proto_msgTypes[75]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5055,7 +5138,7 @@ func (x *GetPeerSSHHostKeyRequest) String() string {
|
||||
func (*GetPeerSSHHostKeyRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetPeerSSHHostKeyRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[73]
|
||||
mi := &file_daemon_proto_msgTypes[75]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5068,7 +5151,7 @@ func (x *GetPeerSSHHostKeyRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetPeerSSHHostKeyRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetPeerSSHHostKeyRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{73}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{75}
|
||||
}
|
||||
|
||||
func (x *GetPeerSSHHostKeyRequest) GetPeerAddress() string {
|
||||
@@ -5095,7 +5178,7 @@ type GetPeerSSHHostKeyResponse struct {
|
||||
|
||||
func (x *GetPeerSSHHostKeyResponse) Reset() {
|
||||
*x = GetPeerSSHHostKeyResponse{}
|
||||
mi := &file_daemon_proto_msgTypes[74]
|
||||
mi := &file_daemon_proto_msgTypes[76]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5107,7 +5190,7 @@ func (x *GetPeerSSHHostKeyResponse) String() string {
|
||||
func (*GetPeerSSHHostKeyResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetPeerSSHHostKeyResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[74]
|
||||
mi := &file_daemon_proto_msgTypes[76]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5120,7 +5203,7 @@ func (x *GetPeerSSHHostKeyResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use GetPeerSSHHostKeyResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetPeerSSHHostKeyResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{74}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{76}
|
||||
}
|
||||
|
||||
func (x *GetPeerSSHHostKeyResponse) GetSshHostKey() []byte {
|
||||
@@ -5162,7 +5245,7 @@ type RequestJWTAuthRequest struct {
|
||||
|
||||
func (x *RequestJWTAuthRequest) Reset() {
|
||||
*x = RequestJWTAuthRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[75]
|
||||
mi := &file_daemon_proto_msgTypes[77]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5174,7 +5257,7 @@ func (x *RequestJWTAuthRequest) String() string {
|
||||
func (*RequestJWTAuthRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RequestJWTAuthRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[75]
|
||||
mi := &file_daemon_proto_msgTypes[77]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5187,7 +5270,7 @@ func (x *RequestJWTAuthRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RequestJWTAuthRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RequestJWTAuthRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{75}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{77}
|
||||
}
|
||||
|
||||
func (x *RequestJWTAuthRequest) GetHint() string {
|
||||
@@ -5220,7 +5303,7 @@ type RequestJWTAuthResponse struct {
|
||||
|
||||
func (x *RequestJWTAuthResponse) Reset() {
|
||||
*x = RequestJWTAuthResponse{}
|
||||
mi := &file_daemon_proto_msgTypes[76]
|
||||
mi := &file_daemon_proto_msgTypes[78]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5232,7 +5315,7 @@ func (x *RequestJWTAuthResponse) String() string {
|
||||
func (*RequestJWTAuthResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RequestJWTAuthResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[76]
|
||||
mi := &file_daemon_proto_msgTypes[78]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5245,7 +5328,7 @@ func (x *RequestJWTAuthResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RequestJWTAuthResponse.ProtoReflect.Descriptor instead.
|
||||
func (*RequestJWTAuthResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{76}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{78}
|
||||
}
|
||||
|
||||
func (x *RequestJWTAuthResponse) GetVerificationURI() string {
|
||||
@@ -5310,7 +5393,7 @@ type WaitJWTTokenRequest struct {
|
||||
|
||||
func (x *WaitJWTTokenRequest) Reset() {
|
||||
*x = WaitJWTTokenRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[77]
|
||||
mi := &file_daemon_proto_msgTypes[79]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5322,7 +5405,7 @@ func (x *WaitJWTTokenRequest) String() string {
|
||||
func (*WaitJWTTokenRequest) ProtoMessage() {}
|
||||
|
||||
func (x *WaitJWTTokenRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[77]
|
||||
mi := &file_daemon_proto_msgTypes[79]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5335,7 +5418,7 @@ func (x *WaitJWTTokenRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use WaitJWTTokenRequest.ProtoReflect.Descriptor instead.
|
||||
func (*WaitJWTTokenRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{77}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{79}
|
||||
}
|
||||
|
||||
func (x *WaitJWTTokenRequest) GetDeviceCode() string {
|
||||
@@ -5367,7 +5450,7 @@ type WaitJWTTokenResponse struct {
|
||||
|
||||
func (x *WaitJWTTokenResponse) Reset() {
|
||||
*x = WaitJWTTokenResponse{}
|
||||
mi := &file_daemon_proto_msgTypes[78]
|
||||
mi := &file_daemon_proto_msgTypes[80]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5379,7 +5462,7 @@ func (x *WaitJWTTokenResponse) String() string {
|
||||
func (*WaitJWTTokenResponse) ProtoMessage() {}
|
||||
|
||||
func (x *WaitJWTTokenResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[78]
|
||||
mi := &file_daemon_proto_msgTypes[80]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5392,7 +5475,7 @@ func (x *WaitJWTTokenResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use WaitJWTTokenResponse.ProtoReflect.Descriptor instead.
|
||||
func (*WaitJWTTokenResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{78}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{80}
|
||||
}
|
||||
|
||||
func (x *WaitJWTTokenResponse) GetToken() string {
|
||||
@@ -5425,7 +5508,7 @@ type StartCPUProfileRequest struct {
|
||||
|
||||
func (x *StartCPUProfileRequest) Reset() {
|
||||
*x = StartCPUProfileRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[79]
|
||||
mi := &file_daemon_proto_msgTypes[81]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5437,7 +5520,7 @@ func (x *StartCPUProfileRequest) String() string {
|
||||
func (*StartCPUProfileRequest) ProtoMessage() {}
|
||||
|
||||
func (x *StartCPUProfileRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[79]
|
||||
mi := &file_daemon_proto_msgTypes[81]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5450,7 +5533,7 @@ func (x *StartCPUProfileRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StartCPUProfileRequest.ProtoReflect.Descriptor instead.
|
||||
func (*StartCPUProfileRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{79}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{81}
|
||||
}
|
||||
|
||||
// StartCPUProfileResponse confirms CPU profiling has started
|
||||
@@ -5462,7 +5545,7 @@ type StartCPUProfileResponse struct {
|
||||
|
||||
func (x *StartCPUProfileResponse) Reset() {
|
||||
*x = StartCPUProfileResponse{}
|
||||
mi := &file_daemon_proto_msgTypes[80]
|
||||
mi := &file_daemon_proto_msgTypes[82]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5474,7 +5557,7 @@ func (x *StartCPUProfileResponse) String() string {
|
||||
func (*StartCPUProfileResponse) ProtoMessage() {}
|
||||
|
||||
func (x *StartCPUProfileResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[80]
|
||||
mi := &file_daemon_proto_msgTypes[82]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5487,7 +5570,7 @@ func (x *StartCPUProfileResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StartCPUProfileResponse.ProtoReflect.Descriptor instead.
|
||||
func (*StartCPUProfileResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{80}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{82}
|
||||
}
|
||||
|
||||
// StopCPUProfileRequest for stopping CPU profiling
|
||||
@@ -5499,7 +5582,7 @@ type StopCPUProfileRequest struct {
|
||||
|
||||
func (x *StopCPUProfileRequest) Reset() {
|
||||
*x = StopCPUProfileRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[81]
|
||||
mi := &file_daemon_proto_msgTypes[83]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5511,7 +5594,7 @@ func (x *StopCPUProfileRequest) String() string {
|
||||
func (*StopCPUProfileRequest) ProtoMessage() {}
|
||||
|
||||
func (x *StopCPUProfileRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[81]
|
||||
mi := &file_daemon_proto_msgTypes[83]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5524,7 +5607,7 @@ func (x *StopCPUProfileRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StopCPUProfileRequest.ProtoReflect.Descriptor instead.
|
||||
func (*StopCPUProfileRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{81}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{83}
|
||||
}
|
||||
|
||||
// StopCPUProfileResponse confirms CPU profiling has stopped
|
||||
@@ -5536,7 +5619,7 @@ type StopCPUProfileResponse struct {
|
||||
|
||||
func (x *StopCPUProfileResponse) Reset() {
|
||||
*x = StopCPUProfileResponse{}
|
||||
mi := &file_daemon_proto_msgTypes[82]
|
||||
mi := &file_daemon_proto_msgTypes[84]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5548,7 +5631,7 @@ func (x *StopCPUProfileResponse) String() string {
|
||||
func (*StopCPUProfileResponse) ProtoMessage() {}
|
||||
|
||||
func (x *StopCPUProfileResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[82]
|
||||
mi := &file_daemon_proto_msgTypes[84]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5561,7 +5644,7 @@ func (x *StopCPUProfileResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StopCPUProfileResponse.ProtoReflect.Descriptor instead.
|
||||
func (*StopCPUProfileResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{82}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{84}
|
||||
}
|
||||
|
||||
type InstallerResultRequest struct {
|
||||
@@ -5572,7 +5655,7 @@ type InstallerResultRequest struct {
|
||||
|
||||
func (x *InstallerResultRequest) Reset() {
|
||||
*x = InstallerResultRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[83]
|
||||
mi := &file_daemon_proto_msgTypes[85]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5584,7 +5667,7 @@ func (x *InstallerResultRequest) String() string {
|
||||
func (*InstallerResultRequest) ProtoMessage() {}
|
||||
|
||||
func (x *InstallerResultRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[83]
|
||||
mi := &file_daemon_proto_msgTypes[85]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5597,7 +5680,7 @@ func (x *InstallerResultRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use InstallerResultRequest.ProtoReflect.Descriptor instead.
|
||||
func (*InstallerResultRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{83}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{85}
|
||||
}
|
||||
|
||||
type InstallerResultResponse struct {
|
||||
@@ -5610,7 +5693,7 @@ type InstallerResultResponse struct {
|
||||
|
||||
func (x *InstallerResultResponse) Reset() {
|
||||
*x = InstallerResultResponse{}
|
||||
mi := &file_daemon_proto_msgTypes[84]
|
||||
mi := &file_daemon_proto_msgTypes[86]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5622,7 +5705,7 @@ func (x *InstallerResultResponse) String() string {
|
||||
func (*InstallerResultResponse) ProtoMessage() {}
|
||||
|
||||
func (x *InstallerResultResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[84]
|
||||
mi := &file_daemon_proto_msgTypes[86]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5635,7 +5718,7 @@ func (x *InstallerResultResponse) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use InstallerResultResponse.ProtoReflect.Descriptor instead.
|
||||
func (*InstallerResultResponse) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{84}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{86}
|
||||
}
|
||||
|
||||
func (x *InstallerResultResponse) GetSuccess() bool {
|
||||
@@ -5661,13 +5744,14 @@ type ExposeServiceRequest struct {
|
||||
UserGroups []string `protobuf:"bytes,5,rep,name=user_groups,json=userGroups,proto3" json:"user_groups,omitempty"`
|
||||
Domain string `protobuf:"bytes,6,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
NamePrefix string `protobuf:"bytes,7,opt,name=name_prefix,json=namePrefix,proto3" json:"name_prefix,omitempty"`
|
||||
ListenPort uint32 `protobuf:"varint,8,opt,name=listen_port,json=listenPort,proto3" json:"listen_port,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExposeServiceRequest) Reset() {
|
||||
*x = ExposeServiceRequest{}
|
||||
mi := &file_daemon_proto_msgTypes[85]
|
||||
mi := &file_daemon_proto_msgTypes[87]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5679,7 +5763,7 @@ func (x *ExposeServiceRequest) String() string {
|
||||
func (*ExposeServiceRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[85]
|
||||
mi := &file_daemon_proto_msgTypes[87]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5692,7 +5776,7 @@ func (x *ExposeServiceRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ExposeServiceRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ExposeServiceRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{85}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{87}
|
||||
}
|
||||
|
||||
func (x *ExposeServiceRequest) GetPort() uint32 {
|
||||
@@ -5744,6 +5828,13 @@ func (x *ExposeServiceRequest) GetNamePrefix() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExposeServiceRequest) GetListenPort() uint32 {
|
||||
if x != nil {
|
||||
return x.ListenPort
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ExposeServiceEvent struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Event:
|
||||
@@ -5756,7 +5847,7 @@ type ExposeServiceEvent struct {
|
||||
|
||||
func (x *ExposeServiceEvent) Reset() {
|
||||
*x = ExposeServiceEvent{}
|
||||
mi := &file_daemon_proto_msgTypes[86]
|
||||
mi := &file_daemon_proto_msgTypes[88]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5768,7 +5859,7 @@ func (x *ExposeServiceEvent) String() string {
|
||||
func (*ExposeServiceEvent) ProtoMessage() {}
|
||||
|
||||
func (x *ExposeServiceEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[86]
|
||||
mi := &file_daemon_proto_msgTypes[88]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5781,7 +5872,7 @@ func (x *ExposeServiceEvent) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ExposeServiceEvent.ProtoReflect.Descriptor instead.
|
||||
func (*ExposeServiceEvent) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{86}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{88}
|
||||
}
|
||||
|
||||
func (x *ExposeServiceEvent) GetEvent() isExposeServiceEvent_Event {
|
||||
@@ -5811,17 +5902,18 @@ type ExposeServiceEvent_Ready struct {
|
||||
func (*ExposeServiceEvent_Ready) isExposeServiceEvent_Event() {}
|
||||
|
||||
type ExposeServiceReady struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"`
|
||||
ServiceUrl string `protobuf:"bytes,2,opt,name=service_url,json=serviceUrl,proto3" json:"service_url,omitempty"`
|
||||
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"`
|
||||
ServiceUrl string `protobuf:"bytes,2,opt,name=service_url,json=serviceUrl,proto3" json:"service_url,omitempty"`
|
||||
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
PortAutoAssigned bool `protobuf:"varint,4,opt,name=port_auto_assigned,json=portAutoAssigned,proto3" json:"port_auto_assigned,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExposeServiceReady) Reset() {
|
||||
*x = ExposeServiceReady{}
|
||||
mi := &file_daemon_proto_msgTypes[87]
|
||||
mi := &file_daemon_proto_msgTypes[89]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5833,7 +5925,7 @@ func (x *ExposeServiceReady) String() string {
|
||||
func (*ExposeServiceReady) ProtoMessage() {}
|
||||
|
||||
func (x *ExposeServiceReady) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[87]
|
||||
mi := &file_daemon_proto_msgTypes[89]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -5846,7 +5938,7 @@ func (x *ExposeServiceReady) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ExposeServiceReady.ProtoReflect.Descriptor instead.
|
||||
func (*ExposeServiceReady) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_proto_rawDescGZIP(), []int{87}
|
||||
return file_daemon_proto_rawDescGZIP(), []int{89}
|
||||
}
|
||||
|
||||
func (x *ExposeServiceReady) GetServiceName() string {
|
||||
@@ -5870,6 +5962,13 @@ func (x *ExposeServiceReady) GetDomain() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExposeServiceReady) GetPortAutoAssigned() bool {
|
||||
if x != nil {
|
||||
return x.PortAutoAssigned
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type PortInfo_Range struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Start uint32 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"`
|
||||
@@ -5880,7 +5979,7 @@ type PortInfo_Range struct {
|
||||
|
||||
func (x *PortInfo_Range) Reset() {
|
||||
*x = PortInfo_Range{}
|
||||
mi := &file_daemon_proto_msgTypes[89]
|
||||
mi := &file_daemon_proto_msgTypes[91]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -5892,7 +5991,7 @@ func (x *PortInfo_Range) String() string {
|
||||
func (*PortInfo_Range) ProtoMessage() {}
|
||||
|
||||
func (x *PortInfo_Range) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_proto_msgTypes[89]
|
||||
mi := &file_daemon_proto_msgTypes[91]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -6016,16 +6115,12 @@ const file_daemon_proto_rawDesc = "" +
|
||||
"\buserCode\x18\x01 \x01(\tR\buserCode\x12\x1a\n" +
|
||||
"\bhostname\x18\x02 \x01(\tR\bhostname\",\n" +
|
||||
"\x14WaitSSOLoginResponse\x12\x14\n" +
|
||||
"\x05email\x18\x01 \x01(\tR\x05email\"\xa4\x01\n" +
|
||||
"\x05email\x18\x01 \x01(\tR\x05email\"v\n" +
|
||||
"\tUpRequest\x12%\n" +
|
||||
"\vprofileName\x18\x01 \x01(\tH\x00R\vprofileName\x88\x01\x01\x12\x1f\n" +
|
||||
"\busername\x18\x02 \x01(\tH\x01R\busername\x88\x01\x01\x12#\n" +
|
||||
"\n" +
|
||||
"autoUpdate\x18\x03 \x01(\bH\x02R\n" +
|
||||
"autoUpdate\x88\x01\x01B\x0e\n" +
|
||||
"\busername\x18\x02 \x01(\tH\x01R\busername\x88\x01\x01B\x0e\n" +
|
||||
"\f_profileNameB\v\n" +
|
||||
"\t_usernameB\r\n" +
|
||||
"\v_autoUpdate\"\f\n" +
|
||||
"\t_usernameJ\x04\b\x03\x10\x04\"\f\n" +
|
||||
"\n" +
|
||||
"UpResponse\"\xa1\x01\n" +
|
||||
"\rStatusRequest\x12,\n" +
|
||||
@@ -6380,7 +6475,11 @@ const file_daemon_proto_rawDesc = "" +
|
||||
"\x12GetFeaturesRequest\"x\n" +
|
||||
"\x13GetFeaturesResponse\x12)\n" +
|
||||
"\x10disable_profiles\x18\x01 \x01(\bR\x0fdisableProfiles\x126\n" +
|
||||
"\x17disable_update_settings\x18\x02 \x01(\bR\x15disableUpdateSettings\"<\n" +
|
||||
"\x17disable_update_settings\x18\x02 \x01(\bR\x15disableUpdateSettings\"\x16\n" +
|
||||
"\x14TriggerUpdateRequest\"M\n" +
|
||||
"\x15TriggerUpdateResponse\x12\x18\n" +
|
||||
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1a\n" +
|
||||
"\berrorMsg\x18\x02 \x01(\tR\berrorMsg\"<\n" +
|
||||
"\x18GetPeerSSHHostKeyRequest\x12 \n" +
|
||||
"\vpeerAddress\x18\x01 \x01(\tR\vpeerAddress\"\x85\x01\n" +
|
||||
"\x19GetPeerSSHHostKeyResponse\x12\x1e\n" +
|
||||
@@ -6419,7 +6518,7 @@ const file_daemon_proto_rawDesc = "" +
|
||||
"\x16InstallerResultRequest\"O\n" +
|
||||
"\x17InstallerResultResponse\x12\x18\n" +
|
||||
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1a\n" +
|
||||
"\berrorMsg\x18\x02 \x01(\tR\berrorMsg\"\xe6\x01\n" +
|
||||
"\berrorMsg\x18\x02 \x01(\tR\berrorMsg\"\x87\x02\n" +
|
||||
"\x14ExposeServiceRequest\x12\x12\n" +
|
||||
"\x04port\x18\x01 \x01(\rR\x04port\x122\n" +
|
||||
"\bprotocol\x18\x02 \x01(\x0e2\x16.daemon.ExposeProtocolR\bprotocol\x12\x10\n" +
|
||||
@@ -6429,15 +6528,18 @@ const file_daemon_proto_rawDesc = "" +
|
||||
"userGroups\x12\x16\n" +
|
||||
"\x06domain\x18\x06 \x01(\tR\x06domain\x12\x1f\n" +
|
||||
"\vname_prefix\x18\a \x01(\tR\n" +
|
||||
"namePrefix\"Q\n" +
|
||||
"namePrefix\x12\x1f\n" +
|
||||
"\vlisten_port\x18\b \x01(\rR\n" +
|
||||
"listenPort\"Q\n" +
|
||||
"\x12ExposeServiceEvent\x122\n" +
|
||||
"\x05ready\x18\x01 \x01(\v2\x1a.daemon.ExposeServiceReadyH\x00R\x05readyB\a\n" +
|
||||
"\x05event\"p\n" +
|
||||
"\x05event\"\x9e\x01\n" +
|
||||
"\x12ExposeServiceReady\x12!\n" +
|
||||
"\fservice_name\x18\x01 \x01(\tR\vserviceName\x12\x1f\n" +
|
||||
"\vservice_url\x18\x02 \x01(\tR\n" +
|
||||
"serviceUrl\x12\x16\n" +
|
||||
"\x06domain\x18\x03 \x01(\tR\x06domain*b\n" +
|
||||
"\x06domain\x18\x03 \x01(\tR\x06domain\x12,\n" +
|
||||
"\x12port_auto_assigned\x18\x04 \x01(\bR\x10portAutoAssigned*b\n" +
|
||||
"\bLogLevel\x12\v\n" +
|
||||
"\aUNKNOWN\x10\x00\x12\t\n" +
|
||||
"\x05PANIC\x10\x01\x12\t\n" +
|
||||
@@ -6446,14 +6548,16 @@ const file_daemon_proto_rawDesc = "" +
|
||||
"\x04WARN\x10\x04\x12\b\n" +
|
||||
"\x04INFO\x10\x05\x12\t\n" +
|
||||
"\x05DEBUG\x10\x06\x12\t\n" +
|
||||
"\x05TRACE\x10\a*S\n" +
|
||||
"\x05TRACE\x10\a*c\n" +
|
||||
"\x0eExposeProtocol\x12\x0f\n" +
|
||||
"\vEXPOSE_HTTP\x10\x00\x12\x10\n" +
|
||||
"\fEXPOSE_HTTPS\x10\x01\x12\x0e\n" +
|
||||
"\n" +
|
||||
"EXPOSE_TCP\x10\x02\x12\x0e\n" +
|
||||
"\n" +
|
||||
"EXPOSE_UDP\x10\x032\xac\x15\n" +
|
||||
"EXPOSE_UDP\x10\x03\x12\x0e\n" +
|
||||
"\n" +
|
||||
"EXPOSE_TLS\x10\x042\xfc\x15\n" +
|
||||
"\rDaemonService\x126\n" +
|
||||
"\x05Login\x12\x14.daemon.LoginRequest\x1a\x15.daemon.LoginResponse\"\x00\x12K\n" +
|
||||
"\fWaitSSOLogin\x12\x1b.daemon.WaitSSOLoginRequest\x1a\x1c.daemon.WaitSSOLoginResponse\"\x00\x12-\n" +
|
||||
@@ -6485,7 +6589,8 @@ const file_daemon_proto_rawDesc = "" +
|
||||
"\fListProfiles\x12\x1b.daemon.ListProfilesRequest\x1a\x1c.daemon.ListProfilesResponse\"\x00\x12W\n" +
|
||||
"\x10GetActiveProfile\x12\x1f.daemon.GetActiveProfileRequest\x1a .daemon.GetActiveProfileResponse\"\x00\x129\n" +
|
||||
"\x06Logout\x12\x15.daemon.LogoutRequest\x1a\x16.daemon.LogoutResponse\"\x00\x12H\n" +
|
||||
"\vGetFeatures\x12\x1a.daemon.GetFeaturesRequest\x1a\x1b.daemon.GetFeaturesResponse\"\x00\x12Z\n" +
|
||||
"\vGetFeatures\x12\x1a.daemon.GetFeaturesRequest\x1a\x1b.daemon.GetFeaturesResponse\"\x00\x12N\n" +
|
||||
"\rTriggerUpdate\x12\x1c.daemon.TriggerUpdateRequest\x1a\x1d.daemon.TriggerUpdateResponse\"\x00\x12Z\n" +
|
||||
"\x11GetPeerSSHHostKey\x12 .daemon.GetPeerSSHHostKeyRequest\x1a!.daemon.GetPeerSSHHostKeyResponse\"\x00\x12Q\n" +
|
||||
"\x0eRequestJWTAuth\x12\x1d.daemon.RequestJWTAuthRequest\x1a\x1e.daemon.RequestJWTAuthResponse\"\x00\x12K\n" +
|
||||
"\fWaitJWTToken\x12\x1b.daemon.WaitJWTTokenRequest\x1a\x1c.daemon.WaitJWTTokenResponse\"\x00\x12T\n" +
|
||||
@@ -6508,7 +6613,7 @@ func file_daemon_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
|
||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 91)
|
||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 93)
|
||||
var file_daemon_proto_goTypes = []any{
|
||||
(LogLevel)(0), // 0: daemon.LogLevel
|
||||
(ExposeProtocol)(0), // 1: daemon.ExposeProtocol
|
||||
@@ -6588,34 +6693,36 @@ var file_daemon_proto_goTypes = []any{
|
||||
(*LogoutResponse)(nil), // 75: daemon.LogoutResponse
|
||||
(*GetFeaturesRequest)(nil), // 76: daemon.GetFeaturesRequest
|
||||
(*GetFeaturesResponse)(nil), // 77: daemon.GetFeaturesResponse
|
||||
(*GetPeerSSHHostKeyRequest)(nil), // 78: daemon.GetPeerSSHHostKeyRequest
|
||||
(*GetPeerSSHHostKeyResponse)(nil), // 79: daemon.GetPeerSSHHostKeyResponse
|
||||
(*RequestJWTAuthRequest)(nil), // 80: daemon.RequestJWTAuthRequest
|
||||
(*RequestJWTAuthResponse)(nil), // 81: daemon.RequestJWTAuthResponse
|
||||
(*WaitJWTTokenRequest)(nil), // 82: daemon.WaitJWTTokenRequest
|
||||
(*WaitJWTTokenResponse)(nil), // 83: daemon.WaitJWTTokenResponse
|
||||
(*StartCPUProfileRequest)(nil), // 84: daemon.StartCPUProfileRequest
|
||||
(*StartCPUProfileResponse)(nil), // 85: daemon.StartCPUProfileResponse
|
||||
(*StopCPUProfileRequest)(nil), // 86: daemon.StopCPUProfileRequest
|
||||
(*StopCPUProfileResponse)(nil), // 87: daemon.StopCPUProfileResponse
|
||||
(*InstallerResultRequest)(nil), // 88: daemon.InstallerResultRequest
|
||||
(*InstallerResultResponse)(nil), // 89: daemon.InstallerResultResponse
|
||||
(*ExposeServiceRequest)(nil), // 90: daemon.ExposeServiceRequest
|
||||
(*ExposeServiceEvent)(nil), // 91: daemon.ExposeServiceEvent
|
||||
(*ExposeServiceReady)(nil), // 92: daemon.ExposeServiceReady
|
||||
nil, // 93: daemon.Network.ResolvedIPsEntry
|
||||
(*PortInfo_Range)(nil), // 94: daemon.PortInfo.Range
|
||||
nil, // 95: daemon.SystemEvent.MetadataEntry
|
||||
(*durationpb.Duration)(nil), // 96: google.protobuf.Duration
|
||||
(*timestamppb.Timestamp)(nil), // 97: google.protobuf.Timestamp
|
||||
(*TriggerUpdateRequest)(nil), // 78: daemon.TriggerUpdateRequest
|
||||
(*TriggerUpdateResponse)(nil), // 79: daemon.TriggerUpdateResponse
|
||||
(*GetPeerSSHHostKeyRequest)(nil), // 80: daemon.GetPeerSSHHostKeyRequest
|
||||
(*GetPeerSSHHostKeyResponse)(nil), // 81: daemon.GetPeerSSHHostKeyResponse
|
||||
(*RequestJWTAuthRequest)(nil), // 82: daemon.RequestJWTAuthRequest
|
||||
(*RequestJWTAuthResponse)(nil), // 83: daemon.RequestJWTAuthResponse
|
||||
(*WaitJWTTokenRequest)(nil), // 84: daemon.WaitJWTTokenRequest
|
||||
(*WaitJWTTokenResponse)(nil), // 85: daemon.WaitJWTTokenResponse
|
||||
(*StartCPUProfileRequest)(nil), // 86: daemon.StartCPUProfileRequest
|
||||
(*StartCPUProfileResponse)(nil), // 87: daemon.StartCPUProfileResponse
|
||||
(*StopCPUProfileRequest)(nil), // 88: daemon.StopCPUProfileRequest
|
||||
(*StopCPUProfileResponse)(nil), // 89: daemon.StopCPUProfileResponse
|
||||
(*InstallerResultRequest)(nil), // 90: daemon.InstallerResultRequest
|
||||
(*InstallerResultResponse)(nil), // 91: daemon.InstallerResultResponse
|
||||
(*ExposeServiceRequest)(nil), // 92: daemon.ExposeServiceRequest
|
||||
(*ExposeServiceEvent)(nil), // 93: daemon.ExposeServiceEvent
|
||||
(*ExposeServiceReady)(nil), // 94: daemon.ExposeServiceReady
|
||||
nil, // 95: daemon.Network.ResolvedIPsEntry
|
||||
(*PortInfo_Range)(nil), // 96: daemon.PortInfo.Range
|
||||
nil, // 97: daemon.SystemEvent.MetadataEntry
|
||||
(*durationpb.Duration)(nil), // 98: google.protobuf.Duration
|
||||
(*timestamppb.Timestamp)(nil), // 99: google.protobuf.Timestamp
|
||||
}
|
||||
var file_daemon_proto_depIdxs = []int32{
|
||||
2, // 0: daemon.OSLifecycleRequest.type:type_name -> daemon.OSLifecycleRequest.CycleType
|
||||
96, // 1: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||
98, // 1: daemon.LoginRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||
28, // 2: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
||||
97, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
||||
97, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
||||
96, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration
|
||||
99, // 3: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
||||
99, // 4: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
||||
98, // 5: daemon.PeerState.latency:type_name -> google.protobuf.Duration
|
||||
26, // 6: daemon.SSHServerState.sessions:type_name -> daemon.SSHSessionInfo
|
||||
23, // 7: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
||||
22, // 8: daemon.FullStatus.signalState:type_name -> daemon.SignalState
|
||||
@@ -6626,8 +6733,8 @@ var file_daemon_proto_depIdxs = []int32{
|
||||
58, // 13: daemon.FullStatus.events:type_name -> daemon.SystemEvent
|
||||
27, // 14: daemon.FullStatus.sshServerState:type_name -> daemon.SSHServerState
|
||||
34, // 15: daemon.ListNetworksResponse.routes:type_name -> daemon.Network
|
||||
93, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry
|
||||
94, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range
|
||||
95, // 16: daemon.Network.resolvedIPs:type_name -> daemon.Network.ResolvedIPsEntry
|
||||
96, // 17: daemon.PortInfo.range:type_name -> daemon.PortInfo.Range
|
||||
35, // 18: daemon.ForwardingRule.destinationPort:type_name -> daemon.PortInfo
|
||||
35, // 19: daemon.ForwardingRule.translatedPort:type_name -> daemon.PortInfo
|
||||
36, // 20: daemon.ForwardingRulesResponse.rules:type_name -> daemon.ForwardingRule
|
||||
@@ -6638,13 +6745,13 @@ var file_daemon_proto_depIdxs = []int32{
|
||||
55, // 25: daemon.TracePacketResponse.stages:type_name -> daemon.TraceStage
|
||||
3, // 26: daemon.SystemEvent.severity:type_name -> daemon.SystemEvent.Severity
|
||||
4, // 27: daemon.SystemEvent.category:type_name -> daemon.SystemEvent.Category
|
||||
97, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
95, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry
|
||||
99, // 28: daemon.SystemEvent.timestamp:type_name -> google.protobuf.Timestamp
|
||||
97, // 29: daemon.SystemEvent.metadata:type_name -> daemon.SystemEvent.MetadataEntry
|
||||
58, // 30: daemon.GetEventsResponse.events:type_name -> daemon.SystemEvent
|
||||
96, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||
98, // 31: daemon.SetConfigRequest.dnsRouteInterval:type_name -> google.protobuf.Duration
|
||||
71, // 32: daemon.ListProfilesResponse.profiles:type_name -> daemon.Profile
|
||||
1, // 33: daemon.ExposeServiceRequest.protocol:type_name -> daemon.ExposeProtocol
|
||||
92, // 34: daemon.ExposeServiceEvent.ready:type_name -> daemon.ExposeServiceReady
|
||||
94, // 34: daemon.ExposeServiceEvent.ready:type_name -> daemon.ExposeServiceReady
|
||||
33, // 35: daemon.Network.ResolvedIPsEntry.value:type_name -> daemon.IPList
|
||||
8, // 36: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
||||
10, // 37: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
||||
@@ -6674,52 +6781,54 @@ var file_daemon_proto_depIdxs = []int32{
|
||||
72, // 61: daemon.DaemonService.GetActiveProfile:input_type -> daemon.GetActiveProfileRequest
|
||||
74, // 62: daemon.DaemonService.Logout:input_type -> daemon.LogoutRequest
|
||||
76, // 63: daemon.DaemonService.GetFeatures:input_type -> daemon.GetFeaturesRequest
|
||||
78, // 64: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
|
||||
80, // 65: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
|
||||
82, // 66: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
|
||||
84, // 67: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
|
||||
86, // 68: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
||||
6, // 69: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest
|
||||
88, // 70: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
||||
90, // 71: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
|
||||
9, // 72: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||
11, // 73: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
||||
13, // 74: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||
15, // 75: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||
17, // 76: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||
19, // 77: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
||||
30, // 78: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
||||
32, // 79: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||
32, // 80: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||
37, // 81: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
||||
39, // 82: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
||||
41, // 83: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
||||
43, // 84: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
||||
46, // 85: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
||||
48, // 86: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
||||
50, // 87: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
||||
52, // 88: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
||||
56, // 89: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
||||
58, // 90: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
||||
60, // 91: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
||||
62, // 92: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
||||
64, // 93: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
||||
66, // 94: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
||||
68, // 95: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
||||
70, // 96: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
||||
73, // 97: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
||||
75, // 98: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
||||
77, // 99: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
||||
79, // 100: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
||||
81, // 101: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
||||
83, // 102: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
||||
85, // 103: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
||||
87, // 104: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
||||
7, // 105: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse
|
||||
89, // 106: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
||||
91, // 107: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
|
||||
72, // [72:108] is the sub-list for method output_type
|
||||
36, // [36:72] is the sub-list for method input_type
|
||||
78, // 64: daemon.DaemonService.TriggerUpdate:input_type -> daemon.TriggerUpdateRequest
|
||||
80, // 65: daemon.DaemonService.GetPeerSSHHostKey:input_type -> daemon.GetPeerSSHHostKeyRequest
|
||||
82, // 66: daemon.DaemonService.RequestJWTAuth:input_type -> daemon.RequestJWTAuthRequest
|
||||
84, // 67: daemon.DaemonService.WaitJWTToken:input_type -> daemon.WaitJWTTokenRequest
|
||||
86, // 68: daemon.DaemonService.StartCPUProfile:input_type -> daemon.StartCPUProfileRequest
|
||||
88, // 69: daemon.DaemonService.StopCPUProfile:input_type -> daemon.StopCPUProfileRequest
|
||||
6, // 70: daemon.DaemonService.NotifyOSLifecycle:input_type -> daemon.OSLifecycleRequest
|
||||
90, // 71: daemon.DaemonService.GetInstallerResult:input_type -> daemon.InstallerResultRequest
|
||||
92, // 72: daemon.DaemonService.ExposeService:input_type -> daemon.ExposeServiceRequest
|
||||
9, // 73: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||
11, // 74: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
||||
13, // 75: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||
15, // 76: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||
17, // 77: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||
19, // 78: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
||||
30, // 79: daemon.DaemonService.ListNetworks:output_type -> daemon.ListNetworksResponse
|
||||
32, // 80: daemon.DaemonService.SelectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||
32, // 81: daemon.DaemonService.DeselectNetworks:output_type -> daemon.SelectNetworksResponse
|
||||
37, // 82: daemon.DaemonService.ForwardingRules:output_type -> daemon.ForwardingRulesResponse
|
||||
39, // 83: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
|
||||
41, // 84: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
|
||||
43, // 85: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
|
||||
46, // 86: daemon.DaemonService.ListStates:output_type -> daemon.ListStatesResponse
|
||||
48, // 87: daemon.DaemonService.CleanState:output_type -> daemon.CleanStateResponse
|
||||
50, // 88: daemon.DaemonService.DeleteState:output_type -> daemon.DeleteStateResponse
|
||||
52, // 89: daemon.DaemonService.SetSyncResponsePersistence:output_type -> daemon.SetSyncResponsePersistenceResponse
|
||||
56, // 90: daemon.DaemonService.TracePacket:output_type -> daemon.TracePacketResponse
|
||||
58, // 91: daemon.DaemonService.SubscribeEvents:output_type -> daemon.SystemEvent
|
||||
60, // 92: daemon.DaemonService.GetEvents:output_type -> daemon.GetEventsResponse
|
||||
62, // 93: daemon.DaemonService.SwitchProfile:output_type -> daemon.SwitchProfileResponse
|
||||
64, // 94: daemon.DaemonService.SetConfig:output_type -> daemon.SetConfigResponse
|
||||
66, // 95: daemon.DaemonService.AddProfile:output_type -> daemon.AddProfileResponse
|
||||
68, // 96: daemon.DaemonService.RemoveProfile:output_type -> daemon.RemoveProfileResponse
|
||||
70, // 97: daemon.DaemonService.ListProfiles:output_type -> daemon.ListProfilesResponse
|
||||
73, // 98: daemon.DaemonService.GetActiveProfile:output_type -> daemon.GetActiveProfileResponse
|
||||
75, // 99: daemon.DaemonService.Logout:output_type -> daemon.LogoutResponse
|
||||
77, // 100: daemon.DaemonService.GetFeatures:output_type -> daemon.GetFeaturesResponse
|
||||
79, // 101: daemon.DaemonService.TriggerUpdate:output_type -> daemon.TriggerUpdateResponse
|
||||
81, // 102: daemon.DaemonService.GetPeerSSHHostKey:output_type -> daemon.GetPeerSSHHostKeyResponse
|
||||
83, // 103: daemon.DaemonService.RequestJWTAuth:output_type -> daemon.RequestJWTAuthResponse
|
||||
85, // 104: daemon.DaemonService.WaitJWTToken:output_type -> daemon.WaitJWTTokenResponse
|
||||
87, // 105: daemon.DaemonService.StartCPUProfile:output_type -> daemon.StartCPUProfileResponse
|
||||
89, // 106: daemon.DaemonService.StopCPUProfile:output_type -> daemon.StopCPUProfileResponse
|
||||
7, // 107: daemon.DaemonService.NotifyOSLifecycle:output_type -> daemon.OSLifecycleResponse
|
||||
91, // 108: daemon.DaemonService.GetInstallerResult:output_type -> daemon.InstallerResultResponse
|
||||
93, // 109: daemon.DaemonService.ExposeService:output_type -> daemon.ExposeServiceEvent
|
||||
73, // [73:110] is the sub-list for method output_type
|
||||
36, // [36:73] is the sub-list for method input_type
|
||||
36, // [36:36] is the sub-list for extension type_name
|
||||
36, // [36:36] is the sub-list for extension extendee
|
||||
0, // [0:36] is the sub-list for field type_name
|
||||
@@ -6742,8 +6851,8 @@ func file_daemon_proto_init() {
|
||||
file_daemon_proto_msgTypes[56].OneofWrappers = []any{}
|
||||
file_daemon_proto_msgTypes[58].OneofWrappers = []any{}
|
||||
file_daemon_proto_msgTypes[69].OneofWrappers = []any{}
|
||||
file_daemon_proto_msgTypes[75].OneofWrappers = []any{}
|
||||
file_daemon_proto_msgTypes[86].OneofWrappers = []any{
|
||||
file_daemon_proto_msgTypes[77].OneofWrappers = []any{}
|
||||
file_daemon_proto_msgTypes[88].OneofWrappers = []any{
|
||||
(*ExposeServiceEvent_Ready)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
@@ -6752,7 +6861,7 @@ func file_daemon_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)),
|
||||
NumEnums: 5,
|
||||
NumMessages: 91,
|
||||
NumMessages: 93,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -85,6 +85,10 @@ service DaemonService {
|
||||
|
||||
rpc GetFeatures(GetFeaturesRequest) returns (GetFeaturesResponse) {}
|
||||
|
||||
// TriggerUpdate initiates installation of the pending enforced version.
|
||||
// Called when the user clicks the install button in the UI (Mode 2 / enforced update).
|
||||
rpc TriggerUpdate(TriggerUpdateRequest) returns (TriggerUpdateResponse) {}
|
||||
|
||||
// GetPeerSSHHostKey retrieves SSH host key for a specific peer
|
||||
rpc GetPeerSSHHostKey(GetPeerSSHHostKeyRequest) returns (GetPeerSSHHostKeyResponse) {}
|
||||
|
||||
@@ -226,7 +230,7 @@ message WaitSSOLoginResponse {
|
||||
message UpRequest {
|
||||
optional string profileName = 1;
|
||||
optional string username = 2;
|
||||
optional bool autoUpdate = 3;
|
||||
reserved 3;
|
||||
}
|
||||
|
||||
message UpResponse {}
|
||||
@@ -725,6 +729,13 @@ message GetFeaturesResponse{
|
||||
bool disable_update_settings = 2;
|
||||
}
|
||||
|
||||
message TriggerUpdateRequest {}
|
||||
|
||||
message TriggerUpdateResponse {
|
||||
bool success = 1;
|
||||
string errorMsg = 2;
|
||||
}
|
||||
|
||||
// GetPeerSSHHostKeyRequest for retrieving SSH host key for a specific peer
|
||||
message GetPeerSSHHostKeyRequest {
|
||||
// peer IP address or FQDN to get SSH host key for
|
||||
@@ -810,6 +821,7 @@ enum ExposeProtocol {
|
||||
EXPOSE_HTTPS = 1;
|
||||
EXPOSE_TCP = 2;
|
||||
EXPOSE_UDP = 3;
|
||||
EXPOSE_TLS = 4;
|
||||
}
|
||||
|
||||
message ExposeServiceRequest {
|
||||
@@ -820,6 +832,7 @@ message ExposeServiceRequest {
|
||||
repeated string user_groups = 5;
|
||||
string domain = 6;
|
||||
string name_prefix = 7;
|
||||
uint32 listen_port = 8;
|
||||
}
|
||||
|
||||
message ExposeServiceEvent {
|
||||
@@ -832,4 +845,5 @@ message ExposeServiceReady {
|
||||
string service_name = 1;
|
||||
string service_url = 2;
|
||||
string domain = 3;
|
||||
bool port_auto_assigned = 4;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ type DaemonServiceClient interface {
|
||||
// Logout disconnects from the network and deletes the peer from the management server
|
||||
Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error)
|
||||
GetFeatures(ctx context.Context, in *GetFeaturesRequest, opts ...grpc.CallOption) (*GetFeaturesResponse, error)
|
||||
// TriggerUpdate initiates installation of the pending enforced version.
|
||||
// Called when the user clicks the install button in the UI (Mode 2 / enforced update).
|
||||
TriggerUpdate(ctx context.Context, in *TriggerUpdateRequest, opts ...grpc.CallOption) (*TriggerUpdateResponse, error)
|
||||
// GetPeerSSHHostKey retrieves SSH host key for a specific peer
|
||||
GetPeerSSHHostKey(ctx context.Context, in *GetPeerSSHHostKeyRequest, opts ...grpc.CallOption) (*GetPeerSSHHostKeyResponse, error)
|
||||
// RequestJWTAuth initiates JWT authentication flow for SSH
|
||||
@@ -363,6 +366,15 @@ func (c *daemonServiceClient) GetFeatures(ctx context.Context, in *GetFeaturesRe
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonServiceClient) TriggerUpdate(ctx context.Context, in *TriggerUpdateRequest, opts ...grpc.CallOption) (*TriggerUpdateResponse, error) {
|
||||
out := new(TriggerUpdateResponse)
|
||||
err := c.cc.Invoke(ctx, "/daemon.DaemonService/TriggerUpdate", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *daemonServiceClient) GetPeerSSHHostKey(ctx context.Context, in *GetPeerSSHHostKeyRequest, opts ...grpc.CallOption) (*GetPeerSSHHostKeyResponse, error) {
|
||||
out := new(GetPeerSSHHostKeyResponse)
|
||||
err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetPeerSSHHostKey", in, out, opts...)
|
||||
@@ -508,6 +520,9 @@ type DaemonServiceServer interface {
|
||||
// Logout disconnects from the network and deletes the peer from the management server
|
||||
Logout(context.Context, *LogoutRequest) (*LogoutResponse, error)
|
||||
GetFeatures(context.Context, *GetFeaturesRequest) (*GetFeaturesResponse, error)
|
||||
// TriggerUpdate initiates installation of the pending enforced version.
|
||||
// Called when the user clicks the install button in the UI (Mode 2 / enforced update).
|
||||
TriggerUpdate(context.Context, *TriggerUpdateRequest) (*TriggerUpdateResponse, error)
|
||||
// GetPeerSSHHostKey retrieves SSH host key for a specific peer
|
||||
GetPeerSSHHostKey(context.Context, *GetPeerSSHHostKeyRequest) (*GetPeerSSHHostKeyResponse, error)
|
||||
// RequestJWTAuth initiates JWT authentication flow for SSH
|
||||
@@ -613,6 +628,9 @@ func (UnimplementedDaemonServiceServer) Logout(context.Context, *LogoutRequest)
|
||||
func (UnimplementedDaemonServiceServer) GetFeatures(context.Context, *GetFeaturesRequest) (*GetFeaturesResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetFeatures not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServiceServer) TriggerUpdate(context.Context, *TriggerUpdateRequest) (*TriggerUpdateResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method TriggerUpdate not implemented")
|
||||
}
|
||||
func (UnimplementedDaemonServiceServer) GetPeerSSHHostKey(context.Context, *GetPeerSSHHostKeyRequest) (*GetPeerSSHHostKeyResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetPeerSSHHostKey not implemented")
|
||||
}
|
||||
@@ -1157,6 +1175,24 @@ func _DaemonService_GetFeatures_Handler(srv interface{}, ctx context.Context, de
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DaemonService_TriggerUpdate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(TriggerUpdateRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DaemonServiceServer).TriggerUpdate(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/daemon.DaemonService/TriggerUpdate",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DaemonServiceServer).TriggerUpdate(ctx, req.(*TriggerUpdateRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _DaemonService_GetPeerSSHHostKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetPeerSSHHostKeyRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -1419,6 +1455,10 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetFeatures",
|
||||
Handler: _DaemonService_GetFeatures_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerUpdate",
|
||||
Handler: _DaemonService_TriggerUpdate_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetPeerSSHHostKey",
|
||||
Handler: _DaemonService_GetPeerSSHHostKey_Handler,
|
||||
|
||||
@@ -14,6 +14,7 @@ func (s *Server) SubscribeEvents(req *proto.SubscribeRequest, stream proto.Daemo
|
||||
}()
|
||||
|
||||
log.Debug("client subscribed to events")
|
||||
s.startUpdateManagerForGUI()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -30,6 +30,8 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||
"github.com/netbirdio/netbird/client/internal/updater"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
@@ -89,6 +91,8 @@ type Server struct {
|
||||
|
||||
sleepHandler *sleephandler.SleepHandler
|
||||
|
||||
updateManager *updater.Manager
|
||||
|
||||
jwtCache *jwtCache
|
||||
}
|
||||
|
||||
@@ -135,6 +139,12 @@ func (s *Server) Start() error {
|
||||
log.Warnf(errRestoreResidualState, err)
|
||||
}
|
||||
|
||||
if s.updateManager == nil {
|
||||
stateMgr := statemanager.New(s.profileManager.GetStatePath())
|
||||
s.updateManager = updater.NewManager(s.statusRecorder, stateMgr)
|
||||
s.updateManager.CheckUpdateSuccess(s.rootCtx)
|
||||
}
|
||||
|
||||
// if current state contains any error, return it
|
||||
// in all other cases we can continue execution only if status is idle and up command was
|
||||
// not in the progress or already successfully established connection.
|
||||
@@ -192,14 +202,14 @@ func (s *Server) Start() error {
|
||||
s.clientRunning = true
|
||||
s.clientRunningChan = make(chan struct{})
|
||||
s.clientGiveUpChan = make(chan struct{})
|
||||
go s.connectWithRetryRuns(ctx, config, s.statusRecorder, false, s.clientRunningChan, s.clientGiveUpChan)
|
||||
go s.connectWithRetryRuns(ctx, config, s.statusRecorder, s.clientRunningChan, s.clientGiveUpChan)
|
||||
return nil
|
||||
}
|
||||
|
||||
// connectWithRetryRuns runs the client connection with a backoff strategy where we retry the operation as additional
|
||||
// mechanism to keep the client connected even when the connection is lost.
|
||||
// we cancel retry if the client receive a stop or down command, or if disable auto connect is configured.
|
||||
func (s *Server) connectWithRetryRuns(ctx context.Context, profileConfig *profilemanager.Config, statusRecorder *peer.Status, doInitialAutoUpdate bool, runningChan chan struct{}, giveUpChan chan struct{}) {
|
||||
func (s *Server) connectWithRetryRuns(ctx context.Context, profileConfig *profilemanager.Config, statusRecorder *peer.Status, runningChan chan struct{}, giveUpChan chan struct{}) {
|
||||
defer func() {
|
||||
s.mutex.Lock()
|
||||
s.clientRunning = false
|
||||
@@ -207,7 +217,7 @@ func (s *Server) connectWithRetryRuns(ctx context.Context, profileConfig *profil
|
||||
}()
|
||||
|
||||
if s.config.DisableAutoConnect {
|
||||
if err := s.connect(ctx, s.config, s.statusRecorder, doInitialAutoUpdate, runningChan); err != nil {
|
||||
if err := s.connect(ctx, s.config, s.statusRecorder, runningChan); err != nil {
|
||||
log.Debugf("run client connection exited with error: %v", err)
|
||||
}
|
||||
log.Tracef("client connection exited")
|
||||
@@ -236,8 +246,7 @@ func (s *Server) connectWithRetryRuns(ctx context.Context, profileConfig *profil
|
||||
}()
|
||||
|
||||
runOperation := func() error {
|
||||
err := s.connect(ctx, profileConfig, statusRecorder, doInitialAutoUpdate, runningChan)
|
||||
doInitialAutoUpdate = false
|
||||
err := s.connect(ctx, profileConfig, statusRecorder, runningChan)
|
||||
if err != nil {
|
||||
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
|
||||
return err
|
||||
@@ -641,8 +650,6 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
|
||||
return s.waitForUp(callerCtx)
|
||||
}
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if err := restoreResidualState(callerCtx, s.profileManager.GetStatePath()); err != nil {
|
||||
log.Warnf(errRestoreResidualState, err)
|
||||
}
|
||||
@@ -654,10 +661,12 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
// not in the progress or already successfully established connection.
|
||||
status, err := state.Status()
|
||||
if err != nil {
|
||||
s.mutex.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if status != internal.StatusIdle {
|
||||
s.mutex.Unlock()
|
||||
return nil, fmt.Errorf("up already in progress: current status %s", status)
|
||||
}
|
||||
|
||||
@@ -674,17 +683,20 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
s.actCancel = cancel
|
||||
|
||||
if s.config == nil {
|
||||
s.mutex.Unlock()
|
||||
return nil, fmt.Errorf("config is not defined, please call login command first")
|
||||
}
|
||||
|
||||
activeProf, err := s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
s.mutex.Unlock()
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
|
||||
if msg != nil && msg.ProfileName != nil {
|
||||
if err := s.switchProfileIfNeeded(*msg.ProfileName, msg.Username, activeProf); err != nil {
|
||||
s.mutex.Unlock()
|
||||
log.Errorf("failed to switch profile: %v", err)
|
||||
return nil, fmt.Errorf("failed to switch profile: %w", err)
|
||||
}
|
||||
@@ -692,6 +704,7 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
|
||||
activeProf, err = s.profileManager.GetActiveProfileState()
|
||||
if err != nil {
|
||||
s.mutex.Unlock()
|
||||
log.Errorf("failed to get active profile state: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile state: %w", err)
|
||||
}
|
||||
@@ -700,6 +713,7 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
|
||||
config, _, err := s.getConfig(activeProf)
|
||||
if err != nil {
|
||||
s.mutex.Unlock()
|
||||
log.Errorf("failed to get active profile config: %v", err)
|
||||
return nil, fmt.Errorf("failed to get active profile config: %w", err)
|
||||
}
|
||||
@@ -712,12 +726,9 @@ func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpR
|
||||
s.clientRunningChan = make(chan struct{})
|
||||
s.clientGiveUpChan = make(chan struct{})
|
||||
|
||||
var doAutoUpdate bool
|
||||
if msg != nil && msg.AutoUpdate != nil && *msg.AutoUpdate {
|
||||
doAutoUpdate = true
|
||||
}
|
||||
go s.connectWithRetryRuns(ctx, s.config, s.statusRecorder, doAutoUpdate, s.clientRunningChan, s.clientGiveUpChan)
|
||||
go s.connectWithRetryRuns(ctx, s.config, s.statusRecorder, s.clientRunningChan, s.clientGiveUpChan)
|
||||
|
||||
s.mutex.Unlock()
|
||||
return s.waitForUp(callerCtx)
|
||||
}
|
||||
|
||||
@@ -843,14 +854,26 @@ func (s *Server) cleanupConnection() error {
|
||||
if s.actCancel == nil {
|
||||
return ErrServiceNotUp
|
||||
}
|
||||
|
||||
// Capture the engine reference before cancelling the context.
|
||||
// After actCancel(), the connectWithRetryRuns goroutine wakes up
|
||||
// and sets connectClient.engine = nil, causing connectClient.Stop()
|
||||
// to skip the engine shutdown entirely.
|
||||
var engine *internal.Engine
|
||||
if s.connectClient != nil {
|
||||
engine = s.connectClient.Engine()
|
||||
}
|
||||
|
||||
s.actCancel()
|
||||
|
||||
if s.connectClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.connectClient.Stop(); err != nil {
|
||||
return err
|
||||
if engine != nil {
|
||||
if err := engine.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.connectClient = nil
|
||||
@@ -1355,9 +1378,10 @@ func (s *Server) ExposeService(req *proto.ExposeServiceRequest, srv proto.Daemon
|
||||
if err := srv.Send(&proto.ExposeServiceEvent{
|
||||
Event: &proto.ExposeServiceEvent_Ready{
|
||||
Ready: &proto.ExposeServiceReady{
|
||||
ServiceName: result.ServiceName,
|
||||
ServiceUrl: result.ServiceURL,
|
||||
Domain: result.Domain,
|
||||
ServiceName: result.ServiceName,
|
||||
ServiceUrl: result.ServiceURL,
|
||||
Domain: result.Domain,
|
||||
PortAutoAssigned: result.PortAutoAssigned,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
@@ -1605,11 +1629,17 @@ func (s *Server) GetFeatures(ctx context.Context, msg *proto.GetFeaturesRequest)
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func (s *Server) connect(ctx context.Context, config *profilemanager.Config, statusRecorder *peer.Status, doInitialAutoUpdate bool, runningChan chan struct{}) error {
|
||||
func (s *Server) connect(ctx context.Context, config *profilemanager.Config, statusRecorder *peer.Status, runningChan chan struct{}) error {
|
||||
log.Tracef("running client connection")
|
||||
s.connectClient = internal.NewConnectClient(ctx, config, statusRecorder, doInitialAutoUpdate)
|
||||
s.connectClient.SetSyncResponsePersistence(s.persistSyncResponse)
|
||||
if err := s.connectClient.Run(runningChan, s.logFile); err != nil {
|
||||
client := internal.NewConnectClient(ctx, config, statusRecorder)
|
||||
client.SetUpdateManager(s.updateManager)
|
||||
client.SetSyncResponsePersistence(s.persistSyncResponse)
|
||||
|
||||
s.mutex.Lock()
|
||||
s.connectClient = client
|
||||
s.mutex.Unlock()
|
||||
|
||||
if err := client.Run(runningChan, s.logFile); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -1633,6 +1663,14 @@ func (s *Server) checkUpdateSettingsDisabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Server) startUpdateManagerForGUI() {
|
||||
if s.updateManager == nil {
|
||||
return
|
||||
}
|
||||
s.updateManager.Start(s.rootCtx)
|
||||
s.updateManager.NotifyUI()
|
||||
}
|
||||
|
||||
func (s *Server) onSessionExpire() {
|
||||
if runtime.GOOS != "windows" {
|
||||
isUIActive := internal.CheckUIApp()
|
||||
|
||||
187
client/server/server_connect_test.go
Normal file
187
client/server/server_connect_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
func newTestServer() *Server {
|
||||
return &Server{
|
||||
rootCtx: context.Background(),
|
||||
statusRecorder: peer.NewRecorder(""),
|
||||
}
|
||||
}
|
||||
|
||||
func newDummyConnectClient(ctx context.Context) *internal.ConnectClient {
|
||||
return internal.NewConnectClient(ctx, nil, nil)
|
||||
}
|
||||
|
||||
// TestConnectSetsClientWithMutex validates that connect() sets s.connectClient
|
||||
// under mutex protection so concurrent readers see a consistent value.
|
||||
func TestConnectSetsClientWithMutex(t *testing.T) {
|
||||
s := newTestServer()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// Manually simulate what connect() does (without calling Run which panics without full setup)
|
||||
client := newDummyConnectClient(ctx)
|
||||
|
||||
s.mutex.Lock()
|
||||
s.connectClient = client
|
||||
s.mutex.Unlock()
|
||||
|
||||
// Verify the assignment is visible under mutex
|
||||
s.mutex.Lock()
|
||||
assert.Equal(t, client, s.connectClient, "connectClient should be set")
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
|
||||
// TestConcurrentConnectClientAccess validates that concurrent reads of
|
||||
// s.connectClient under mutex don't race with a write.
|
||||
func TestConcurrentConnectClientAccess(t *testing.T) {
|
||||
s := newTestServer()
|
||||
ctx := context.Background()
|
||||
client := newDummyConnectClient(ctx)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
nilCount := 0
|
||||
setCount := 0
|
||||
var mu sync.Mutex
|
||||
|
||||
// Start readers
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s.mutex.Lock()
|
||||
c := s.connectClient
|
||||
s.mutex.Unlock()
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if c == nil {
|
||||
nilCount++
|
||||
} else {
|
||||
setCount++
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Simulate connect() writing under mutex
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
s.mutex.Lock()
|
||||
s.connectClient = client
|
||||
s.mutex.Unlock()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, 50, nilCount+setCount, "all goroutines should complete without panic")
|
||||
}
|
||||
|
||||
// TestCleanupConnection_ClearsConnectClient validates that cleanupConnection
|
||||
// properly nils out connectClient.
|
||||
func TestCleanupConnection_ClearsConnectClient(t *testing.T) {
|
||||
s := newTestServer()
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
s.actCancel = cancel
|
||||
|
||||
s.connectClient = newDummyConnectClient(context.Background())
|
||||
s.clientRunning = true
|
||||
|
||||
err := s.cleanupConnection()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Nil(t, s.connectClient, "connectClient should be nil after cleanup")
|
||||
}
|
||||
|
||||
// TestCleanState_NilConnectClient validates that CleanState doesn't panic
|
||||
// when connectClient is nil.
|
||||
func TestCleanState_NilConnectClient(t *testing.T) {
|
||||
s := newTestServer()
|
||||
s.connectClient = nil
|
||||
s.profileManager = nil // will cause error if it tries to proceed past the nil check
|
||||
|
||||
// Should not panic — the nil check should prevent calling Status() on nil
|
||||
assert.NotPanics(t, func() {
|
||||
_, _ = s.CleanState(context.Background(), &proto.CleanStateRequest{All: true})
|
||||
})
|
||||
}
|
||||
|
||||
// TestDeleteState_NilConnectClient validates that DeleteState doesn't panic
|
||||
// when connectClient is nil.
|
||||
func TestDeleteState_NilConnectClient(t *testing.T) {
|
||||
s := newTestServer()
|
||||
s.connectClient = nil
|
||||
s.profileManager = nil
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
_, _ = s.DeleteState(context.Background(), &proto.DeleteStateRequest{All: true})
|
||||
})
|
||||
}
|
||||
|
||||
// TestDownThenUp_StaleRunningChan documents the known state issue where
|
||||
// clientRunningChan from a previous connection is already closed, causing
|
||||
// waitForUp() to return immediately on reconnect.
|
||||
func TestDownThenUp_StaleRunningChan(t *testing.T) {
|
||||
s := newTestServer()
|
||||
|
||||
// Simulate state after a successful connection
|
||||
s.clientRunning = true
|
||||
s.clientRunningChan = make(chan struct{})
|
||||
close(s.clientRunningChan) // closed when engine started
|
||||
s.clientGiveUpChan = make(chan struct{})
|
||||
s.connectClient = newDummyConnectClient(context.Background())
|
||||
|
||||
_, cancel := context.WithCancel(context.Background())
|
||||
s.actCancel = cancel
|
||||
|
||||
// Simulate Down(): cleanupConnection sets connectClient = nil
|
||||
s.mutex.Lock()
|
||||
err := s.cleanupConnection()
|
||||
s.mutex.Unlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
// After cleanup: connectClient is nil, clientRunning still true
|
||||
// (goroutine hasn't exited yet)
|
||||
s.mutex.Lock()
|
||||
assert.Nil(t, s.connectClient, "connectClient should be nil after cleanup")
|
||||
assert.True(t, s.clientRunning, "clientRunning still true until goroutine exits")
|
||||
s.mutex.Unlock()
|
||||
|
||||
// waitForUp() returns immediately due to stale closed clientRunningChan
|
||||
ctx, ctxCancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer ctxCancel()
|
||||
|
||||
waitDone := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := s.waitForUp(ctx)
|
||||
waitDone <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-waitDone:
|
||||
assert.NoError(t, err, "waitForUp returns success on stale channel")
|
||||
// But connectClient is still nil — this is the stale state issue
|
||||
s.mutex.Lock()
|
||||
assert.Nil(t, s.connectClient, "connectClient is nil despite waitForUp success")
|
||||
s.mutex.Unlock()
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatal("waitForUp should have returned immediately due to stale closed channel")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConnectClient_EngineNilOnFreshClient validates that a newly created
|
||||
// ConnectClient has nil Engine (before Run is called).
|
||||
func TestConnectClient_EngineNilOnFreshClient(t *testing.T) {
|
||||
client := newDummyConnectClient(context.Background())
|
||||
assert.Nil(t, client.Engine(), "engine should be nil on fresh ConnectClient")
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func TestConnectWithRetryRuns(t *testing.T) {
|
||||
t.Setenv(maxRetryTimeVar, "5s")
|
||||
t.Setenv(retryMultiplierVar, "1")
|
||||
|
||||
s.connectWithRetryRuns(ctx, config, s.statusRecorder, false, nil, nil)
|
||||
s.connectWithRetryRuns(ctx, config, s.statusRecorder, nil, nil)
|
||||
if counter < 3 {
|
||||
t.Fatalf("expected counter > 2, got %d", counter)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func (s *Server) ListStates(_ context.Context, _ *proto.ListStatesRequest) (*pro
|
||||
|
||||
// CleanState handles cleaning of states (performing cleanup operations)
|
||||
func (s *Server) CleanState(ctx context.Context, req *proto.CleanStateRequest) (*proto.CleanStateResponse, error) {
|
||||
if s.connectClient.Status() == internal.StatusConnected || s.connectClient.Status() == internal.StatusConnecting {
|
||||
if s.connectClient != nil && (s.connectClient.Status() == internal.StatusConnected || s.connectClient.Status() == internal.StatusConnecting) {
|
||||
return nil, status.Errorf(codes.FailedPrecondition, "cannot clean state while connecting or connected, run 'netbird down' first.")
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func (s *Server) CleanState(ctx context.Context, req *proto.CleanStateRequest) (
|
||||
|
||||
// DeleteState handles deletion of states without cleanup
|
||||
func (s *Server) DeleteState(ctx context.Context, req *proto.DeleteStateRequest) (*proto.DeleteStateResponse, error) {
|
||||
if s.connectClient.Status() == internal.StatusConnected || s.connectClient.Status() == internal.StatusConnecting {
|
||||
if s.connectClient != nil && (s.connectClient.Status() == internal.StatusConnected || s.connectClient.Status() == internal.StatusConnecting) {
|
||||
return nil, status.Errorf(codes.FailedPrecondition, "cannot clean state while connecting or connected, run 'netbird down' first.")
|
||||
}
|
||||
|
||||
|
||||
24
client/server/triggerupdate.go
Normal file
24
client/server/triggerupdate.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
// TriggerUpdate initiates installation of the pending enforced version.
|
||||
// It is called when the user clicks the install button in the UI (Mode 2 / enforced update).
|
||||
func (s *Server) TriggerUpdate(ctx context.Context, _ *proto.TriggerUpdateRequest) (*proto.TriggerUpdateResponse, error) {
|
||||
if s.updateManager == nil {
|
||||
return &proto.TriggerUpdateResponse{Success: false, ErrorMsg: "update manager not available"}, nil
|
||||
}
|
||||
|
||||
if err := s.updateManager.Install(ctx); err != nil {
|
||||
log.Warnf("TriggerUpdate failed: %v", err)
|
||||
return &proto.TriggerUpdateResponse{Success: false, ErrorMsg: err.Error()}, nil
|
||||
}
|
||||
|
||||
return &proto.TriggerUpdateResponse{Success: true}, nil
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/updatemanager/installer"
|
||||
"github.com/netbirdio/netbird/client/internal/updater/installer"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
)
|
||||
|
||||
|
||||
24
client/ssh/server/getent_cgo_unix.go
Normal file
24
client/ssh/server/getent_cgo_unix.go
Normal file
@@ -0,0 +1,24 @@
|
||||
//go:build cgo && !osusergo && !windows
|
||||
|
||||
package server
|
||||
|
||||
import "os/user"
|
||||
|
||||
// lookupWithGetent with CGO delegates directly to os/user.Lookup.
|
||||
// When CGO is enabled, os/user uses libc (getpwnam_r) which goes through
|
||||
// the NSS stack natively. If it fails, the user truly doesn't exist and
|
||||
// getent would also fail.
|
||||
func lookupWithGetent(username string) (*user.User, error) {
|
||||
return user.Lookup(username)
|
||||
}
|
||||
|
||||
// currentUserWithGetent with CGO delegates directly to os/user.Current.
|
||||
func currentUserWithGetent() (*user.User, error) {
|
||||
return user.Current()
|
||||
}
|
||||
|
||||
// groupIdsWithFallback with CGO delegates directly to user.GroupIds.
|
||||
// libc's getgrouplist handles NSS groups natively.
|
||||
func groupIdsWithFallback(u *user.User) ([]string, error) {
|
||||
return u.GroupIds()
|
||||
}
|
||||
74
client/ssh/server/getent_nocgo_unix.go
Normal file
74
client/ssh/server/getent_nocgo_unix.go
Normal file
@@ -0,0 +1,74 @@
|
||||
//go:build (!cgo || osusergo) && !windows
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// lookupWithGetent looks up a user by name, falling back to getent if os/user fails.
|
||||
// Without CGO, os/user only reads /etc/passwd and misses NSS-provided users.
|
||||
// getent goes through the host's NSS stack.
|
||||
func lookupWithGetent(username string) (*user.User, error) {
|
||||
u, err := user.Lookup(username)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
stdErr := err
|
||||
log.Debugf("os/user.Lookup(%q) failed, trying getent: %v", username, err)
|
||||
|
||||
u, _, getentErr := runGetent(username)
|
||||
if getentErr != nil {
|
||||
log.Debugf("getent fallback for %q also failed: %v", username, getentErr)
|
||||
return nil, stdErr
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// currentUserWithGetent gets the current user, falling back to getent if os/user fails.
|
||||
func currentUserWithGetent() (*user.User, error) {
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
stdErr := err
|
||||
uid := strconv.Itoa(os.Getuid())
|
||||
log.Debugf("os/user.Current() failed, trying getent with UID %s: %v", uid, err)
|
||||
|
||||
u, _, getentErr := runGetent(uid)
|
||||
if getentErr != nil {
|
||||
return nil, stdErr
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// groupIdsWithFallback gets group IDs for a user via the id command first,
|
||||
// falling back to user.GroupIds().
|
||||
// NOTE: unlike lookupWithGetent/currentUserWithGetent which try stdlib first,
|
||||
// this intentionally tries `id -G` first because without CGO, user.GroupIds()
|
||||
// only reads /etc/group and silently returns incomplete results for NSS users
|
||||
// (no error, just missing groups). The id command goes through NSS and returns
|
||||
// the full set.
|
||||
func groupIdsWithFallback(u *user.User) ([]string, error) {
|
||||
ids, err := runIdGroups(u.Username)
|
||||
if err == nil {
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
log.Debugf("id -G %q failed, falling back to user.GroupIds(): %v", u.Username, err)
|
||||
|
||||
ids, stdErr := u.GroupIds()
|
||||
if stdErr != nil {
|
||||
return nil, stdErr
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user