mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-26 10:39:56 +00:00
Compare commits
6 Commits
main
...
feat/dev_v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a23722f459 | ||
|
|
496c459870 | ||
|
|
0546c55b1a | ||
|
|
c820a3a7f3 | ||
|
|
461f1cd96a | ||
|
|
67e4a13713 |
@@ -12,7 +12,13 @@ var (
|
|||||||
Short: "Print the NetBird's client application version",
|
Short: "Print the NetBird's client application version",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cmd.SetOut(cmd.OutOrStdout())
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
cmd.Println(version.NetbirdVersion())
|
out := version.NetbirdVersion()
|
||||||
|
if version.IsDevelopmentVersion(out) {
|
||||||
|
if commit := version.NetbirdCommit(); commit != "" {
|
||||||
|
out += "-" + commit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Println(out)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
|
||||||
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
@@ -101,26 +100,6 @@ type Options struct {
|
|||||||
MTU *uint16
|
MTU *uint16
|
||||||
// DNSLabels defines additional DNS labels configured in the peer.
|
// DNSLabels defines additional DNS labels configured in the peer.
|
||||||
DNSLabels []string
|
DNSLabels []string
|
||||||
// Performance configures the tunnel's buffer pool cap and batch size.
|
|
||||||
Performance Performance
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance configures the embedded client's tunnel memory/throughput knobs.
|
|
||||||
//
|
|
||||||
// These settings are process-global: any non-nil field also becomes the
|
|
||||||
// default for Clients constructed by later embed.New calls in the same
|
|
||||||
// process. Nil fields are ignored.
|
|
||||||
type Performance struct {
|
|
||||||
// PreallocatedBuffersPerPool caps the per-tunnel buffer pool. Zero
|
|
||||||
// leaves the pool unbounded. Lower values trade throughput for a
|
|
||||||
// tighter memory ceiling. May also be changed on a running Client via
|
|
||||||
// Client.SetPerformance, provided this field was nonzero at construction.
|
|
||||||
PreallocatedBuffersPerPool *uint32
|
|
||||||
// MaxBatchSize overrides the number of packets the tunnel reads or
|
|
||||||
// writes per syscall, which also bounds eager buffer allocation per
|
|
||||||
// worker. Zero uses the platform default. Applied at construction
|
|
||||||
// only; ignored by Client.SetPerformance.
|
|
||||||
MaxBatchSize *uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateCredentials checks that exactly one credential type is provided
|
// validateCredentials checks that exactly one credential type is provided
|
||||||
@@ -220,13 +199,6 @@ func New(opts Options) (*Client, error) {
|
|||||||
config.PrivateKey = opts.PrivateKey
|
config.PrivateKey = opts.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Performance.PreallocatedBuffersPerPool != nil {
|
|
||||||
wgdevice.SetPreallocatedBuffersPerPool(*opts.Performance.PreallocatedBuffersPerPool)
|
|
||||||
}
|
|
||||||
if opts.Performance.MaxBatchSize != nil {
|
|
||||||
wgdevice.SetMaxBatchSizeOverride(*opts.Performance.MaxBatchSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
deviceName: opts.DeviceName,
|
deviceName: opts.DeviceName,
|
||||||
setupKey: opts.SetupKey,
|
setupKey: opts.SetupKey,
|
||||||
@@ -523,25 +495,6 @@ func (c *Client) VerifySSHHostKey(peerAddress string, key []byte) error {
|
|||||||
return sshcommon.VerifyHostKey(storedKey, key, peerAddress)
|
return sshcommon.VerifyHostKey(storedKey, key, peerAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPerformance retunes a running Client. Only PreallocatedBuffersPerPool
|
|
||||||
// takes effect, and only when it was nonzero at construction;
|
|
||||||
// MaxBatchSize is construction-only and returns an error if set here.
|
|
||||||
//
|
|
||||||
// Returns ErrClientNotStarted / ErrEngineNotStarted if the Client is not
|
|
||||||
// running yet.
|
|
||||||
func (c *Client) SetPerformance(t Performance) error {
|
|
||||||
if t.MaxBatchSize != nil {
|
|
||||||
return errors.New("MaxBatchSize is construction-only and cannot be changed at runtime")
|
|
||||||
}
|
|
||||||
engine, err := c.getEngine()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return engine.SetPerformance(internal.Performance{
|
|
||||||
PreallocatedBuffersPerPool: t.PreallocatedBuffersPerPool,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartCapture begins capturing packets on this client's tunnel device.
|
// StartCapture begins capturing packets on this client's tunnel device.
|
||||||
// Only one capture can be active at a time; starting a new one stops the previous.
|
// Only one capture can be active at a time; starting a new one stops the previous.
|
||||||
// Call StopCapture (or CaptureSession.Stop) to end it.
|
// Call StopCapture (or CaptureSession.Stop) to end it.
|
||||||
|
|||||||
@@ -1967,29 +1967,6 @@ func (e *Engine) GetClientMetrics() *metrics.ClientMetrics {
|
|||||||
return e.clientMetrics
|
return e.clientMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performance bundles runtime-adjustable tunnel pool knobs.
|
|
||||||
// See Engine.SetPerformance. Nil fields are ignored.
|
|
||||||
type Performance struct {
|
|
||||||
PreallocatedBuffersPerPool *uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPerformance applies the given tuning to this engine's live Device.
|
|
||||||
func (e *Engine) SetPerformance(t Performance) error {
|
|
||||||
e.syncMsgMux.Lock()
|
|
||||||
defer e.syncMsgMux.Unlock()
|
|
||||||
if e.wgInterface == nil {
|
|
||||||
return fmt.Errorf("wg interface not initialized")
|
|
||||||
}
|
|
||||||
dev := e.wgInterface.GetWGDevice()
|
|
||||||
if dev == nil {
|
|
||||||
return fmt.Errorf("wg device not initialized")
|
|
||||||
}
|
|
||||||
if t.PreallocatedBuffersPerPool != nil {
|
|
||||||
dev.SetPreallocatedBuffersPerPool(*t.PreallocatedBuffersPerPool)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
|
|
||||||
|
nbversion "github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -11,7 +13,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func IsSupported(agentVersion string) bool {
|
func IsSupported(agentVersion string) bool {
|
||||||
if agentVersion == "development" {
|
if nbversion.IsDevelopmentVersion(agentVersion) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
latestVersion = "latest"
|
latestVersion = "latest"
|
||||||
// this version will be ignored
|
|
||||||
developmentVersion = "development"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNoUpdateState = errors.New("no update state found")
|
var errNoUpdateState = errors.New("no update state found")
|
||||||
@@ -483,7 +481,7 @@ func (m *Manager) loadAndDeleteUpdateState(ctx context.Context) (*UpdateState, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) shouldUpdate(updateVersion *v.Version, forceUpdate bool) bool {
|
func (m *Manager) shouldUpdate(updateVersion *v.Version, forceUpdate bool) bool {
|
||||||
if m.currentVersion == developmentVersion {
|
if version.IsDevelopmentVersion(m.currentVersion) {
|
||||||
log.Debugf("skipping auto-update, running development version")
|
log.Debugf("skipping auto-update, running development version")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -335,7 +335,7 @@ replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-2024
|
|||||||
|
|
||||||
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949
|
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949
|
||||||
|
|
||||||
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f
|
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0
|
||||||
|
|
||||||
replace github.com/cloudflare/circl => codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
replace github.com/cloudflare/circl => codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
||||||
|
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -499,8 +499,8 @@ github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9ax
|
|||||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ=
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ=
|
||||||
github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f h1:ff2D57RBjWtyQ2wVwJOxOgXAXOe/J2lJWtSX0Bz/BRk=
|
github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0 h1:h/QnNzm7xzHPm+gajcblYUOclrW2FeNeDlUNj6tTWKQ=
|
||||||
github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
@@ -510,7 +511,7 @@ func computeForwarderPort(peers []*nbpeer.Peer, requiredVersion string) int64 {
|
|||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
|
|
||||||
// Development version is always supported
|
// Development version is always supported
|
||||||
if peer.Meta.WtVersion == "development" {
|
if version.IsDevelopmentVersion(peer.Meta.WtVersion) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
peerVersion := semver.Canonical("v" + peer.Meta.WtVersion)
|
peerVersion := semver.Canonical("v" + peer.Meta.WtVersion)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const remoteJobsMinVer = "0.64.0"
|
const remoteJobsMinVer = "0.64.0"
|
||||||
@@ -372,7 +373,7 @@ func (am *DefaultAccountManager) CreatePeerJob(ctx context.Context, accountID, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
meetMinVer, err := posture.MeetsMinVersion(remoteJobsMinVer, p.Meta.WtVersion)
|
meetMinVer, err := posture.MeetsMinVersion(remoteJobsMinVer, p.Meta.WtVersion)
|
||||||
if !strings.Contains(p.Meta.WtVersion, "dev") && (!meetMinVer || err != nil) {
|
if !version.IsDevelopmentVersion(p.Meta.WtVersion) && (!meetMinVer || err != nil) {
|
||||||
return status.Errorf(status.PreconditionFailed, "peer version %s does not meet the minimum required version %s for remote jobs", p.Meta.WtVersion, remoteJobsMinVer)
|
return status.Errorf(status.PreconditionFailed, "peer version %s does not meet the minimum required version %s for remote jobs", p.Meta.WtVersion, remoteJobsMinVer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -1804,7 +1805,7 @@ func shouldCheckRulesForNativeSSH(supportsNative bool, rule *PolicyRule, peer *n
|
|||||||
|
|
||||||
// peerSupportedFirewallFeatures checks if the peer version supports port ranges.
|
// peerSupportedFirewallFeatures checks if the peer version supports port ranges.
|
||||||
func peerSupportedFirewallFeatures(peerVer string) supportedFeatures {
|
func peerSupportedFirewallFeatures(peerVer string) supportedFeatures {
|
||||||
if strings.Contains(peerVer, "dev") {
|
if version.IsDevelopmentVersion(peerVer) {
|
||||||
return supportedFeatures{true, true}
|
return supportedFeatures{true, true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -646,41 +646,7 @@ func Test_ExpandPortsAndRanges_SSHRuleExpansion(t *testing.T) {
|
|||||||
expectedPorts: []string{"20-25", "10-100", "22022"},
|
expectedPorts: []string{"20-25", "10-100", "22022"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dev suffix version supports all features",
|
name: "development version supports all features",
|
||||||
peer: &nbpeer.Peer{
|
|
||||||
ID: "peer1",
|
|
||||||
SSHEnabled: true,
|
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
|
||||||
WtVersion: "0.50.0-dev",
|
|
||||||
Flags: nbpeer.Flags{ServerSSHAllowed: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rule: &PolicyRule{
|
|
||||||
Protocol: PolicyRuleProtocolTCP,
|
|
||||||
Ports: []string{"22"},
|
|
||||||
},
|
|
||||||
base: FirewallRule{PeerIP: "10.0.0.1", Direction: 0, Action: "accept", Protocol: "tcp"},
|
|
||||||
expectedPorts: []string{"22", "22022"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dev suffix version supports all features",
|
|
||||||
peer: &nbpeer.Peer{
|
|
||||||
ID: "peer1",
|
|
||||||
SSHEnabled: true,
|
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
|
||||||
WtVersion: "dev",
|
|
||||||
Flags: nbpeer.Flags{ServerSSHAllowed: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rule: &PolicyRule{
|
|
||||||
Protocol: PolicyRuleProtocolTCP,
|
|
||||||
Ports: []string{"22"},
|
|
||||||
},
|
|
||||||
base: FirewallRule{PeerIP: "10.0.0.1", Direction: 0, Action: "accept", Protocol: "tcp"},
|
|
||||||
expectedPorts: []string{"22", "22022"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "development suffix version supports all features",
|
|
||||||
peer: &nbpeer.Peer{
|
peer: &nbpeer.Peer{
|
||||||
ID: "peer1",
|
ID: "peer1",
|
||||||
SSHEnabled: true,
|
SSHEnabled: true,
|
||||||
|
|||||||
@@ -109,22 +109,6 @@ var debugStopCmd = &cobra.Command{
|
|||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var debugPerfCmd = &cobra.Command{
|
|
||||||
Use: "perf <pool-cap>",
|
|
||||||
Short: "Live-retune the tunnel buffer pool cap on all running clients",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
RunE: runDebugPerfSet,
|
|
||||||
SilenceUsage: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var debugRuntimeCmd = &cobra.Command{
|
|
||||||
Use: "runtime",
|
|
||||||
Short: "Show runtime stats (heap, goroutines, RSS)",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: runDebugRuntime,
|
|
||||||
SilenceUsage: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var debugCaptureCmd = &cobra.Command{
|
var debugCaptureCmd = &cobra.Command{
|
||||||
Use: "capture <account-id> [filter expression]",
|
Use: "capture <account-id> [filter expression]",
|
||||||
Short: "Capture packets on a client's WireGuard interface",
|
Short: "Capture packets on a client's WireGuard interface",
|
||||||
@@ -175,8 +159,6 @@ func init() {
|
|||||||
debugCmd.AddCommand(debugLogCmd)
|
debugCmd.AddCommand(debugLogCmd)
|
||||||
debugCmd.AddCommand(debugStartCmd)
|
debugCmd.AddCommand(debugStartCmd)
|
||||||
debugCmd.AddCommand(debugStopCmd)
|
debugCmd.AddCommand(debugStopCmd)
|
||||||
debugCmd.AddCommand(debugPerfCmd)
|
|
||||||
debugCmd.AddCommand(debugRuntimeCmd)
|
|
||||||
debugCmd.AddCommand(debugCaptureCmd)
|
debugCmd.AddCommand(debugCaptureCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(debugCmd)
|
rootCmd.AddCommand(debugCmd)
|
||||||
@@ -238,18 +220,6 @@ func runDebugStop(cmd *cobra.Command, args []string) error {
|
|||||||
return getDebugClient(cmd).StopClient(cmd.Context(), args[0])
|
return getDebugClient(cmd).StopClient(cmd.Context(), args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDebugPerfSet(cmd *cobra.Command, args []string) error {
|
|
||||||
n, err := strconv.ParseUint(args[0], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value %q: %w", args[0], err)
|
|
||||||
}
|
|
||||||
return getDebugClient(cmd).PerfSet(cmd.Context(), uint32(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDebugRuntime(cmd *cobra.Command, _ []string) error {
|
|
||||||
return getDebugClient(cmd).Runtime(cmd.Context())
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDebugCapture(cmd *cobra.Command, args []string) error {
|
func runDebugCapture(cmd *cobra.Command, args []string) error {
|
||||||
duration, _ := cmd.Flags().GetDuration("duration")
|
duration, _ := cmd.Flags().GetDuration("duration")
|
||||||
forcePcap, _ := cmd.Flags().GetBool("pcap")
|
forcePcap, _ := cmd.Flags().GetBool("pcap")
|
||||||
|
|||||||
@@ -15,22 +15,11 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/embed"
|
|
||||||
"github.com/netbirdio/netbird/proxy"
|
"github.com/netbirdio/netbird/proxy"
|
||||||
nbacme "github.com/netbirdio/netbird/proxy/internal/acme"
|
nbacme "github.com/netbirdio/netbird/proxy/internal/acme"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// envPreallocatedBuffers caps the per-tunnel buffer pool. Zero (unset)
|
|
||||||
// keeps the upstream uncapped default.
|
|
||||||
envPreallocatedBuffers = "NB_PROXY_PREALLOCATED_BUFFERS"
|
|
||||||
// envMaxBatchSize overrides the per-tunnel batch size, which controls
|
|
||||||
// how many buffers each receive/TUN worker eagerly allocates. Zero
|
|
||||||
// (unset) keeps the platform default.
|
|
||||||
envMaxBatchSize = "NB_PROXY_MAX_BATCH_SIZE"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultManagementURL = "https://api.netbird.io:443"
|
const DefaultManagementURL = "https://api.netbird.io:443"
|
||||||
|
|
||||||
// envProxyToken is the environment variable name for the proxy access token.
|
// envProxyToken is the environment variable name for the proxy access token.
|
||||||
@@ -159,45 +148,6 @@ func runServer(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
logger.Infof("configured log level: %s", level)
|
logger.Infof("configured log level: %s", level)
|
||||||
|
|
||||||
var wgPool, wgBatch uint64
|
|
||||||
var perf embed.Performance
|
|
||||||
if raw := os.Getenv(envPreallocatedBuffers); raw != "" {
|
|
||||||
n, err := strconv.ParseUint(raw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid %s %q: %w", envPreallocatedBuffers, raw, err)
|
|
||||||
}
|
|
||||||
wgPool = n
|
|
||||||
v := uint32(n)
|
|
||||||
perf.PreallocatedBuffersPerPool = &v
|
|
||||||
logger.Infof("tunnel preallocated buffers per pool: %d", n)
|
|
||||||
}
|
|
||||||
if raw := os.Getenv(envMaxBatchSize); raw != "" {
|
|
||||||
n, err := strconv.ParseUint(raw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid %s %q: %w", envMaxBatchSize, raw, err)
|
|
||||||
}
|
|
||||||
wgBatch = n
|
|
||||||
v := uint32(n)
|
|
||||||
perf.MaxBatchSize = &v
|
|
||||||
logger.Infof("tunnel max batch size override: %d", n)
|
|
||||||
}
|
|
||||||
if wgPool > 0 {
|
|
||||||
// Each bind recv goroutine (IPv4 + IPv6 + ICE relay) plus
|
|
||||||
// RoutineReadFromTUN eagerly reserves `batch` message buffers for
|
|
||||||
// the lifetime of the Device. A pool cap below that floor blocks
|
|
||||||
// the receive pipeline at startup.
|
|
||||||
batch := wgBatch
|
|
||||||
if batch == 0 {
|
|
||||||
batch = 128
|
|
||||||
}
|
|
||||||
const recvGoroutines = 4
|
|
||||||
floor := batch * recvGoroutines
|
|
||||||
if wgPool < floor {
|
|
||||||
logger.Warnf("%s=%d is below the eager-allocation floor (~%d for batch=%d); startup may deadlock",
|
|
||||||
envPreallocatedBuffers, wgPool, floor, batch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch forwardedProto {
|
switch forwardedProto {
|
||||||
case "auto", "http", "https":
|
case "auto", "http", "https":
|
||||||
default:
|
default:
|
||||||
@@ -238,7 +188,6 @@ func runServer(cmd *cobra.Command, args []string) error {
|
|||||||
CertLockMethod: nbacme.CertLockMethod(certLockMethod),
|
CertLockMethod: nbacme.CertLockMethod(certLockMethod),
|
||||||
WildcardCertDir: wildcardCertDir,
|
WildcardCertDir: wildcardCertDir,
|
||||||
WireguardPort: wgPort,
|
WireguardPort: wgPort,
|
||||||
Performance: perf,
|
|
||||||
ProxyProtocol: proxyProtocol,
|
ProxyProtocol: proxyProtocol,
|
||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
SupportsCustomPorts: supportsCustomPorts,
|
SupportsCustomPorts: supportsCustomPorts,
|
||||||
|
|||||||
@@ -333,63 +333,6 @@ func (c *Client) printLogLevelResult(data map[string]any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerfSet live-retunes the tunnel buffer pool cap on all running embedded
|
|
||||||
// clients. Batch size is not live-tunable; configure it at proxy startup.
|
|
||||||
func (c *Client) PerfSet(ctx context.Context, value uint32) error {
|
|
||||||
path := fmt.Sprintf("/debug/perf?value=%d", value)
|
|
||||||
return c.fetchAndPrint(ctx, path, c.printPerfSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) printPerfSet(data map[string]any) {
|
|
||||||
if errMsg, ok := data["error"].(string); ok && errMsg != "" {
|
|
||||||
c.printError(data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val, _ := data["value"].(float64)
|
|
||||||
applied, _ := data["applied"].(float64)
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Pool cap set to: %d\n", uint32(val))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Applied to %d live clients\n", int(applied))
|
|
||||||
if failed, ok := data["failed"].(map[string]any); ok && len(failed) > 0 {
|
|
||||||
_, _ = fmt.Fprintln(c.out, "Failed:")
|
|
||||||
for k, v := range failed {
|
|
||||||
_, _ = fmt.Fprintf(c.out, " %s: %v\n", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runtime fetches runtime stats (heap, goroutines, RSS).
|
|
||||||
func (c *Client) Runtime(ctx context.Context) error {
|
|
||||||
return c.fetchAndPrint(ctx, "/debug/runtime", c.printRuntime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) printRuntime(data map[string]any) {
|
|
||||||
i := func(k string) uint64 {
|
|
||||||
v, _ := data[k].(float64)
|
|
||||||
return uint64(v)
|
|
||||||
}
|
|
||||||
mb := func(n uint64) string { return fmt.Sprintf("%.1f MB", float64(n)/(1<<20)) }
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Uptime: %v\n", data["uptime"])
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Go: %v on %d CPU (GOMAXPROCS=%d)\n", data["go_version"], uint32(i("num_cpu")), uint32(i("gomaxprocs")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Goroutines: %d\n", i("goroutines"))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Live objects: %d\n", i("live_objects"))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "GC: %d cycles, %v pause total\n", i("num_gc"), time.Duration(i("pause_total_ns")))
|
|
||||||
_, _ = fmt.Fprintln(c.out, "Heap:")
|
|
||||||
_, _ = fmt.Fprintf(c.out, " alloc: %s\n", mb(i("heap_alloc")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " in-use: %s\n", mb(i("heap_inuse")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " idle: %s\n", mb(i("heap_idle")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " released: %s\n", mb(i("heap_released")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " sys: %s\n", mb(i("heap_sys")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Total sys: %s\n", mb(i("sys")))
|
|
||||||
if _, ok := data["vm_rss"]; ok {
|
|
||||||
_, _ = fmt.Fprintln(c.out, "Process:")
|
|
||||||
_, _ = fmt.Fprintf(c.out, " VmRSS: %s\n", mb(i("vm_rss")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " VmSize: %s\n", mb(i("vm_size")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " VmData: %s\n", mb(i("vm_data")))
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Clients: %d (%d started)\n", i("clients"), i("started"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartClient starts a specific client.
|
// StartClient starts a specific client.
|
||||||
func (c *Client) StartClient(ctx context.Context, accountID string) error {
|
func (c *Client) StartClient(ctx context.Context, accountID string) error {
|
||||||
path := "/debug/clients/" + url.PathEscape(accountID) + "/start"
|
path := "/debug/clients/" + url.PathEscape(accountID) + "/start"
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -61,7 +59,6 @@ func sortedAccountIDs(m map[types.AccountID]roundtrip.ClientDebugInfo) []types.A
|
|||||||
type clientProvider interface {
|
type clientProvider interface {
|
||||||
GetClient(accountID types.AccountID) (*nbembed.Client, bool)
|
GetClient(accountID types.AccountID) (*nbembed.Client, bool)
|
||||||
ListClientsForDebug() map[types.AccountID]roundtrip.ClientDebugInfo
|
ListClientsForDebug() map[types.AccountID]roundtrip.ClientDebugInfo
|
||||||
ListClientsForStartup() map[types.AccountID]*nbembed.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InboundListenerInfo describes a per-account inbound listener as
|
// InboundListenerInfo describes a per-account inbound listener as
|
||||||
@@ -168,10 +165,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.handleListClients(w, r, wantJSON)
|
h.handleListClients(w, r, wantJSON)
|
||||||
case "/debug/health":
|
case "/debug/health":
|
||||||
h.handleHealth(w, r, wantJSON)
|
h.handleHealth(w, r, wantJSON)
|
||||||
case "/debug/perf":
|
|
||||||
h.handlePerf(w, r)
|
|
||||||
case "/debug/runtime":
|
|
||||||
h.handleRuntime(w, r)
|
|
||||||
default:
|
default:
|
||||||
if h.handleClientRoutes(w, r, path, wantJSON) {
|
if h.handleClientRoutes(w, r, path, wantJSON) {
|
||||||
return
|
return
|
||||||
@@ -265,10 +258,10 @@ func (h *Handler) handleIndex(w http.ResponseWriter, _ *http.Request, wantJSON b
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wantJSON {
|
if wantJSON {
|
||||||
clientsJSON := make([]map[string]any, 0, len(clients))
|
clientsJSON := make([]map[string]interface{}, 0, len(clients))
|
||||||
for _, id := range sortedIDs {
|
for _, id := range sortedIDs {
|
||||||
info := clients[id]
|
info := clients[id]
|
||||||
clientsJSON = append(clientsJSON, map[string]any{
|
clientsJSON = append(clientsJSON, map[string]interface{}{
|
||||||
"account_id": info.AccountID,
|
"account_id": info.AccountID,
|
||||||
"service_count": info.ServiceCount,
|
"service_count": info.ServiceCount,
|
||||||
"service_keys": info.ServiceKeys,
|
"service_keys": info.ServiceKeys,
|
||||||
@@ -277,7 +270,7 @@ func (h *Handler) handleIndex(w http.ResponseWriter, _ *http.Request, wantJSON b
|
|||||||
"age": time.Since(info.CreatedAt).Round(time.Second).String(),
|
"age": time.Since(info.CreatedAt).Round(time.Second).String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
resp := map[string]any{
|
resp := map[string]interface{}{
|
||||||
"version": version.NetbirdVersion(),
|
"version": version.NetbirdVersion(),
|
||||||
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
||||||
"client_count": len(clients),
|
"client_count": len(clients),
|
||||||
@@ -359,10 +352,10 @@ func (h *Handler) handleListClients(w http.ResponseWriter, _ *http.Request, want
|
|||||||
if h.inbound != nil {
|
if h.inbound != nil {
|
||||||
inboundAll = h.inbound.InboundListeners()
|
inboundAll = h.inbound.InboundListeners()
|
||||||
}
|
}
|
||||||
clientsJSON := make([]map[string]any, 0, len(clients))
|
clientsJSON := make([]map[string]interface{}, 0, len(clients))
|
||||||
for _, id := range sortedIDs {
|
for _, id := range sortedIDs {
|
||||||
info := clients[id]
|
info := clients[id]
|
||||||
row := map[string]any{
|
row := map[string]interface{}{
|
||||||
"account_id": info.AccountID,
|
"account_id": info.AccountID,
|
||||||
"service_count": info.ServiceCount,
|
"service_count": info.ServiceCount,
|
||||||
"service_keys": info.ServiceKeys,
|
"service_keys": info.ServiceKeys,
|
||||||
@@ -375,7 +368,7 @@ func (h *Handler) handleListClients(w http.ResponseWriter, _ *http.Request, want
|
|||||||
}
|
}
|
||||||
clientsJSON = append(clientsJSON, row)
|
clientsJSON = append(clientsJSON, row)
|
||||||
}
|
}
|
||||||
resp := map[string]any{
|
resp := map[string]interface{}{
|
||||||
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
||||||
"client_count": len(clients),
|
"client_count": len(clients),
|
||||||
"clients": clientsJSON,
|
"clients": clientsJSON,
|
||||||
@@ -465,7 +458,7 @@ func (h *Handler) handleClientStatus(w http.ResponseWriter, r *http.Request, acc
|
|||||||
})
|
})
|
||||||
|
|
||||||
if wantJSON {
|
if wantJSON {
|
||||||
resp := map[string]any{
|
resp := map[string]interface{}{
|
||||||
"account_id": accountID,
|
"account_id": accountID,
|
||||||
"status": overview.FullDetailSummary(),
|
"status": overview.FullDetailSummary(),
|
||||||
}
|
}
|
||||||
@@ -564,20 +557,20 @@ func (h *Handler) handleClientTools(w http.ResponseWriter, _ *http.Request, acco
|
|||||||
func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
host := r.URL.Query().Get("host")
|
host := r.URL.Query().Get("host")
|
||||||
portStr := r.URL.Query().Get("port")
|
portStr := r.URL.Query().Get("port")
|
||||||
if host == "" || portStr == "" {
|
if host == "" || portStr == "" {
|
||||||
h.writeJSON(w, map[string]any{"error": "host and port parameters required"})
|
h.writeJSON(w, map[string]interface{}{"error": "host and port parameters required"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
if err != nil || port < 1 || port > 65535 {
|
if err != nil || port < 1 || port > 65535 {
|
||||||
h.writeJSON(w, map[string]any{"error": "invalid port"})
|
h.writeJSON(w, map[string]interface{}{"error": "invalid port"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,7 +594,7 @@ func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountI
|
|||||||
|
|
||||||
conn, err := client.Dial(ctx, network, address)
|
conn, err := client.Dial(ctx, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"host": host,
|
"host": host,
|
||||||
"port": port,
|
"port": port,
|
||||||
@@ -616,38 +609,39 @@ func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountI
|
|||||||
}
|
}
|
||||||
|
|
||||||
latency := time.Since(start)
|
latency := time.Since(start)
|
||||||
h.writeJSON(w, map[string]any{
|
resp := map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"host": host,
|
"host": host,
|
||||||
"port": port,
|
"port": port,
|
||||||
"remote": remote,
|
"remote": remote,
|
||||||
"latency_ms": latency.Milliseconds(),
|
"latency_ms": latency.Milliseconds(),
|
||||||
"latency": formatDuration(latency),
|
"latency": formatDuration(latency),
|
||||||
})
|
}
|
||||||
|
h.writeJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleLogLevel(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handleLogLevel(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
level := r.URL.Query().Get("level")
|
level := r.URL.Query().Get("level")
|
||||||
if level == "" {
|
if level == "" {
|
||||||
h.writeJSON(w, map[string]any{"error": "level parameter required (trace, debug, info, warn, error)"})
|
h.writeJSON(w, map[string]interface{}{"error": "level parameter required (trace, debug, info, warn, error)"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.SetLogLevel(level); err != nil {
|
if err := client.SetLogLevel(level); err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"level": level,
|
"level": level,
|
||||||
})
|
})
|
||||||
@@ -658,7 +652,7 @@ const clientActionTimeout = 30 * time.Second
|
|||||||
func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,14 +660,14 @@ func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, acco
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := client.Start(ctx); err != nil {
|
if err := client.Start(ctx); err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "client started",
|
"message": "client started",
|
||||||
})
|
})
|
||||||
@@ -682,7 +676,7 @@ func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, acco
|
|||||||
func (h *Handler) handleClientStop(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handleClientStop(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,125 +684,19 @@ func (h *Handler) handleClientStop(w http.ResponseWriter, r *http.Request, accou
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := client.Stop(ctx); err != nil {
|
if err := client.Stop(ctx); err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "client stopped",
|
"message": "client stopped",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handlePerf(w http.ResponseWriter, r *http.Request) {
|
|
||||||
raw := r.URL.Query().Get("value")
|
|
||||||
if raw == "" {
|
|
||||||
http.Error(w, "value parameter is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n, err := strconv.ParseUint(raw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("invalid value %q: %v", raw, err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
capN := uint32(n)
|
|
||||||
applied := 0
|
|
||||||
failed := map[string]string{}
|
|
||||||
for accountID, client := range h.provider.ListClientsForStartup() {
|
|
||||||
if err := client.SetPerformance(nbembed.Performance{PreallocatedBuffersPerPool: &capN}); err != nil {
|
|
||||||
failed[string(accountID)] = err.Error()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
applied++
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := map[string]any{
|
|
||||||
"success": true,
|
|
||||||
"value": capN,
|
|
||||||
"applied": applied,
|
|
||||||
}
|
|
||||||
if len(failed) > 0 {
|
|
||||||
resp["failed"] = failed
|
|
||||||
}
|
|
||||||
h.writeJSON(w, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleRuntime returns cheap runtime and process stats. Safe to hit on a
|
|
||||||
// running proxy; does not read pprof profiles.
|
|
||||||
func (h *Handler) handleRuntime(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
var m runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&m)
|
|
||||||
|
|
||||||
clients := h.provider.ListClientsForDebug()
|
|
||||||
started := 0
|
|
||||||
for _, c := range clients {
|
|
||||||
if c.HasClient {
|
|
||||||
started++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := map[string]any{
|
|
||||||
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
|
||||||
"goroutines": runtime.NumGoroutine(),
|
|
||||||
"num_cpu": runtime.NumCPU(),
|
|
||||||
"gomaxprocs": runtime.GOMAXPROCS(0),
|
|
||||||
"go_version": runtime.Version(),
|
|
||||||
"heap_alloc": m.HeapAlloc,
|
|
||||||
"heap_inuse": m.HeapInuse,
|
|
||||||
"heap_idle": m.HeapIdle,
|
|
||||||
"heap_released": m.HeapReleased,
|
|
||||||
"heap_sys": m.HeapSys,
|
|
||||||
"sys": m.Sys,
|
|
||||||
"live_objects": m.Mallocs - m.Frees,
|
|
||||||
"num_gc": m.NumGC,
|
|
||||||
"pause_total_ns": m.PauseTotalNs,
|
|
||||||
"clients": len(clients),
|
|
||||||
"started": started,
|
|
||||||
}
|
|
||||||
|
|
||||||
if proc := readProcStatus(); proc != nil {
|
|
||||||
resp["vm_rss"] = proc["VmRSS"]
|
|
||||||
resp["vm_size"] = proc["VmSize"]
|
|
||||||
resp["vm_data"] = proc["VmData"]
|
|
||||||
}
|
|
||||||
|
|
||||||
h.writeJSON(w, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readProcStatus parses /proc/self/status on Linux and returns size fields
|
|
||||||
// in bytes. Returns nil on non-Linux or read failure.
|
|
||||||
func readProcStatus() map[string]uint64 {
|
|
||||||
raw, err := os.ReadFile("/proc/self/status")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := map[string]uint64{}
|
|
||||||
for _, line := range strings.Split(string(raw), "\n") {
|
|
||||||
k, v, ok := strings.Cut(line, ":")
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if k != "VmRSS" && k != "VmSize" && k != "VmData" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Fields(v)
|
|
||||||
if len(fields) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := strconv.ParseUint(fields[0], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Values are reported in kB.
|
|
||||||
out[k] = n * 1024
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxCaptureDuration = 30 * time.Minute
|
const maxCaptureDuration = 30 * time.Minute
|
||||||
|
|
||||||
// handleCapture streams a pcap or text packet capture for the given client.
|
// handleCapture streams a pcap or text packet capture for the given client.
|
||||||
@@ -937,7 +825,7 @@ func (h *Handler) handleHealth(w http.ResponseWriter, r *http.Request, wantJSON
|
|||||||
h.writeJSON(w, resp)
|
h.writeJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) renderTemplate(w http.ResponseWriter, name string, data any) {
|
func (h *Handler) renderTemplate(w http.ResponseWriter, name string, data interface{}) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
tmpl := h.getTemplates()
|
tmpl := h.getTemplates()
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
@@ -950,7 +838,7 @@ func (h *Handler) renderTemplate(w http.ResponseWriter, name string, data any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) writeJSON(w http.ResponseWriter, v any) {
|
func (h *Handler) writeJSON(w http.ResponseWriter, v interface{}) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ type ClientConfig struct {
|
|||||||
MgmtAddr string
|
MgmtAddr string
|
||||||
WGPort uint16
|
WGPort uint16
|
||||||
PreSharedKey string
|
PreSharedKey string
|
||||||
Performance embed.Performance
|
|
||||||
// BlockInbound mirrors embed.Options.BlockInbound. Set to true on the
|
// BlockInbound mirrors embed.Options.BlockInbound. Set to true on the
|
||||||
// standalone proxy where the embedded client never accepts inbound;
|
// standalone proxy where the embedded client never accepts inbound;
|
||||||
// set to false on the private/embedded proxy so the engine creates
|
// set to false on the private/embedded proxy so the engine creates
|
||||||
@@ -307,7 +306,7 @@ func (n *NetBird) createClientEntry(ctx context.Context, accountID types.Account
|
|||||||
ManagementURL: n.clientCfg.MgmtAddr,
|
ManagementURL: n.clientCfg.MgmtAddr,
|
||||||
PrivateKey: privateKey.String(),
|
PrivateKey: privateKey.String(),
|
||||||
LogLevel: log.WarnLevel.String(),
|
LogLevel: log.WarnLevel.String(),
|
||||||
BlockInbound: n.clientCfg.BlockInbound,
|
BlockInbound: n.clientCfg.BlockInbound,
|
||||||
// The embedded proxy peer must never be a stepping stone into
|
// The embedded proxy peer must never be a stepping stone into
|
||||||
// the proxy host's LAN: it only exists to reach NetBird mesh
|
// the proxy host's LAN: it only exists to reach NetBird mesh
|
||||||
// targets or, when direct_upstream is set, the host network
|
// targets or, when direct_upstream is set, the host network
|
||||||
@@ -316,7 +315,6 @@ func (n *NetBird) createClientEntry(ctx context.Context, accountID types.Account
|
|||||||
BlockLANAccess: true,
|
BlockLANAccess: true,
|
||||||
WireguardPort: &wgPort,
|
WireguardPort: &wgPort,
|
||||||
PreSharedKey: n.clientCfg.PreSharedKey,
|
PreSharedKey: n.clientCfg.PreSharedKey,
|
||||||
Performance: n.clientCfg.Performance,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create netbird client: %w", err)
|
return nil, fmt.Errorf("create netbird client: %w", err)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/embed"
|
|
||||||
"github.com/netbirdio/netbird/proxy/internal/acme"
|
"github.com/netbirdio/netbird/proxy/internal/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,10 +89,6 @@ type Config struct {
|
|||||||
// PreSharedKey is the WireGuard pre-shared key used between the
|
// PreSharedKey is the WireGuard pre-shared key used between the
|
||||||
// proxy's embedded clients and peers.
|
// proxy's embedded clients and peers.
|
||||||
PreSharedKey string
|
PreSharedKey string
|
||||||
// Performance configures the tunnel pool/batch sizes for every
|
|
||||||
// embedded client this proxy creates. Zero values fall back to
|
|
||||||
// upstream defaults.
|
|
||||||
Performance embed.Performance
|
|
||||||
|
|
||||||
// SupportsCustomPorts indicates whether the proxy can bind arbitrary
|
// SupportsCustomPorts indicates whether the proxy can bind arbitrary
|
||||||
// ports for TCP/UDP/TLS services.
|
// ports for TCP/UDP/TLS services.
|
||||||
@@ -153,7 +148,6 @@ func New(cfg Config) *Server {
|
|||||||
WireguardPort: cfg.WireguardPort,
|
WireguardPort: cfg.WireguardPort,
|
||||||
ProxyProtocol: cfg.ProxyProtocol,
|
ProxyProtocol: cfg.ProxyProtocol,
|
||||||
PreSharedKey: cfg.PreSharedKey,
|
PreSharedKey: cfg.PreSharedKey,
|
||||||
Performance: cfg.Performance,
|
|
||||||
SupportsCustomPorts: cfg.SupportsCustomPorts,
|
SupportsCustomPorts: cfg.SupportsCustomPorts,
|
||||||
RequireSubdomain: cfg.RequireSubdomain,
|
RequireSubdomain: cfg.RequireSubdomain,
|
||||||
Private: cfg.Private,
|
Private: cfg.Private,
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import (
|
|||||||
goproto "google.golang.org/protobuf/proto"
|
goproto "google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/embed"
|
|
||||||
"github.com/netbirdio/netbird/proxy/internal/accesslog"
|
"github.com/netbirdio/netbird/proxy/internal/accesslog"
|
||||||
"github.com/netbirdio/netbird/proxy/internal/acme"
|
"github.com/netbirdio/netbird/proxy/internal/acme"
|
||||||
"github.com/netbirdio/netbird/proxy/internal/auth"
|
"github.com/netbirdio/netbird/proxy/internal/auth"
|
||||||
@@ -186,9 +185,6 @@ type Server struct {
|
|||||||
// single-account deployments; multiple accounts will fail to bind
|
// single-account deployments; multiple accounts will fail to bind
|
||||||
// the same port.
|
// the same port.
|
||||||
WireguardPort uint16
|
WireguardPort uint16
|
||||||
// Performance configures the tunnel pool/batch sizes for every
|
|
||||||
// embedded client this proxy spawns.
|
|
||||||
Performance embed.Performance
|
|
||||||
// ProxyProtocol enables PROXY protocol (v1/v2) on TCP listeners.
|
// ProxyProtocol enables PROXY protocol (v1/v2) on TCP listeners.
|
||||||
// When enabled, the real client IP is extracted from the PROXY header
|
// When enabled, the real client IP is extracted from the PROXY header
|
||||||
// sent by upstream L4 proxies that support PROXY protocol.
|
// sent by upstream L4 proxies that support PROXY protocol.
|
||||||
@@ -337,8 +333,6 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
s.runCancel = runCancel
|
s.runCancel = runCancel
|
||||||
|
|
||||||
s.initNetBirdClient()
|
s.initNetBirdClient()
|
||||||
// Create health checker before the mapping worker so it can track
|
|
||||||
// management connectivity from the first stream connection.
|
|
||||||
s.healthChecker = health.NewChecker(s.Logger, s.netbird)
|
s.healthChecker = health.NewChecker(s.Logger, s.netbird)
|
||||||
|
|
||||||
s.crowdsecRegistry = crowdsec.NewRegistry(s.CrowdSecAPIURL, s.CrowdSecAPIKey, log.NewEntry(s.Logger))
|
s.crowdsecRegistry = crowdsec.NewRegistry(s.CrowdSecAPIURL, s.CrowdSecAPIKey, log.NewEntry(s.Logger))
|
||||||
@@ -535,7 +529,6 @@ func (s *Server) initNetBirdClient() {
|
|||||||
MgmtAddr: s.ManagementAddress,
|
MgmtAddr: s.ManagementAddress,
|
||||||
WGPort: s.WireguardPort,
|
WGPort: s.WireguardPort,
|
||||||
PreSharedKey: s.PreSharedKey,
|
PreSharedKey: s.PreSharedKey,
|
||||||
Performance: s.Performance,
|
|
||||||
// On --private the embedded client serves per-account inbound
|
// On --private the embedded client serves per-account inbound
|
||||||
// listeners and must apply management's ACL: keep BlockInbound off
|
// listeners and must apply management's ACL: keep BlockInbound off
|
||||||
// so the engine creates the ACL manager. On the standalone proxy
|
// so the engine creates the ACL manager. On the standalone proxy
|
||||||
|
|||||||
@@ -2,19 +2,75 @@ package version
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
v "github.com/hashicorp/go-version"
|
v "github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DevelopmentVersion is the value of NetbirdVersion() for non-release builds.
|
||||||
|
// Wire-format consumers (management server, dashboard) match against this
|
||||||
|
// string, so it must not change without coordinating those consumers.
|
||||||
|
const DevelopmentVersion = "development"
|
||||||
|
|
||||||
// will be replaced with the release version when using goreleaser
|
// will be replaced with the release version when using goreleaser
|
||||||
var version = "development"
|
var version = DevelopmentVersion
|
||||||
|
|
||||||
var (
|
var (
|
||||||
VersionRegexp = regexp.MustCompile("^" + v.VersionRegexpRaw + "$")
|
VersionRegexp = regexp.MustCompile("^" + v.VersionRegexpRaw + "$")
|
||||||
SemverRegexp = regexp.MustCompile("^" + v.SemverRegexpRaw + "$")
|
SemverRegexp = regexp.MustCompile("^" + v.SemverRegexpRaw + "$")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NetbirdVersion returns the Netbird version
|
// NetbirdVersion returns the Netbird version. For non-release builds the
|
||||||
|
// value is the literal DevelopmentVersion constant; the VCS revision is
|
||||||
|
// exposed separately via NetbirdCommit so the wire format stays stable.
|
||||||
func NetbirdVersion() string {
|
func NetbirdVersion() string {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetbirdCommit returns the VCS revision (truncated to 12 chars) of the
|
||||||
|
// build, with a "-dirty" suffix when the working tree was modified.
|
||||||
|
// Returns an empty string when no build info is embedded (e.g. release
|
||||||
|
// builds compiled by goreleaser without -buildvcs).
|
||||||
|
func NetbirdCommit() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var revision string
|
||||||
|
var modified bool
|
||||||
|
for _, s := range info.Settings {
|
||||||
|
switch s.Key {
|
||||||
|
case "vcs.revision":
|
||||||
|
revision = s.Value
|
||||||
|
case "vcs.modified":
|
||||||
|
modified = s.Value == "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(revision) > 12 {
|
||||||
|
revision = revision[:12]
|
||||||
|
}
|
||||||
|
|
||||||
|
if modified {
|
||||||
|
revision += "-dirty"
|
||||||
|
}
|
||||||
|
return revision
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDevelopmentVersion reports whether the given version string identifies
|
||||||
|
// a non-release / development build. It is the single source of truth for
|
||||||
|
// "is this a dev build" checks across the codebase; use it instead of
|
||||||
|
// comparing against the "development" literal or ad-hoc substring checks.
|
||||||
|
//
|
||||||
|
// Matches the bare DevelopmentVersion constant as well as any future
|
||||||
|
// extension such as "development-<sha>" or "development-<sha>-dirty",
|
||||||
|
// while excluding tagged prereleases like "v0.31.1-dev".
|
||||||
|
func IsDevelopmentVersion(v string) bool {
|
||||||
|
return strings.HasPrefix(v, DevelopmentVersion)
|
||||||
|
}
|
||||||
|
|||||||
26
version/version_test.go
Normal file
26
version/version_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsDevelopmentVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
version string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"development", true},
|
||||||
|
{"development-0823f3ff9ab1", true},
|
||||||
|
{"development-0823f3ff9ab1-dirty", true},
|
||||||
|
{"0.50.0", false},
|
||||||
|
{"v0.31.1-dev", false},
|
||||||
|
{"1.0.0-dev", false},
|
||||||
|
{"dev", false},
|
||||||
|
{"", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.version, func(t *testing.T) {
|
||||||
|
if got := IsDevelopmentVersion(tt.version); got != tt.want {
|
||||||
|
t.Errorf("IsDevelopmentVersion(%q) = %v, want %v", tt.version, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user