Merge branch 'main' into otel

This commit is contained in:
Marc Schäfer
2025-10-10 19:41:08 +02:00
committed by GitHub
18 changed files with 571 additions and 97 deletions

View File

@@ -0,0 +1,47 @@
body:
- type: textarea
attributes:
label: Summary
description: A clear and concise summary of the requested feature.
validations:
required: true
- type: textarea
attributes:
label: Motivation
description: |
Why is this feature important?
Explain the problem this feature would solve or what use case it would enable.
validations:
required: true
- type: textarea
attributes:
label: Proposed Solution
description: |
How would you like to see this feature implemented?
Provide as much detail as possible about the desired behavior, configuration, or changes.
validations:
required: true
- type: textarea
attributes:
label: Alternatives Considered
description: Describe any alternative solutions or workarounds you've thought about.
validations:
required: false
- type: textarea
attributes:
label: Additional Context
description: Add any other context, mockups, or screenshots about the feature request here.
validations:
required: false
- type: markdown
attributes:
value: |
Before submitting, please:
- Check if there is an existing issue for this feature.
- Clearly explain the benefit and use case.
- Be as specific as possible to help contributors evaluate and implement.

51
.github/ISSUE_TEMPLATE/1.bug_report.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Bug Report
description: Create a bug report
labels: []
body:
- type: textarea
attributes:
label: Describe the Bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Environment
description: Please fill out the relevant details below for your environment.
value: |
- OS Type & Version: (e.g., Ubuntu 22.04)
- Pangolin Version:
- Gerbil Version:
- Traefik Version:
- Newt Version:
- Olm Version: (if applicable)
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: |
Steps to reproduce the behavior, please provide a clear description of how to reproduce the issue, based on the linked minimal reproduction. Screenshots can be provided in the issue body below.
If using code blocks, make sure syntax highlighting is correct and double-check that the rendered preview is not broken.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: markdown
attributes:
value: |
Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear.
- type: markdown
attributes:
value: |
Contributors should be able to follow the steps provided in order to reproduce the bug.

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Need help or have questions?
url: https://github.com/orgs/fosrl/discussions
about: Ask questions, get help, and discuss with other community members
- name: Request a Feature
url: https://github.com/orgs/fosrl/discussions/new?category=feature-requests
about: Feature requests should be opened as discussions so others can upvote and comment

View File

@@ -34,7 +34,7 @@ jobs:
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: 1.25 go-version: 1.25

View File

@@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: 1.25 go-version: 1.25

View File

@@ -59,6 +59,8 @@ When Newt receives WireGuard control messages, it will use the information encod
- `native` (optional): Use native WireGuard interface when accepting clients (requires WireGuard kernel module and Linux, must run as root). Default: false (uses userspace netstack) - `native` (optional): Use native WireGuard interface when accepting clients (requires WireGuard kernel module and Linux, must run as root). Default: false (uses userspace netstack)
- `interface` (optional): Name of the WireGuard interface. Default: newt - `interface` (optional): Name of the WireGuard interface. Default: newt
- `keep-interface` (optional): Keep the WireGuard interface. Default: false - `keep-interface` (optional): Keep the WireGuard interface. Default: false
- `blueprint-file` (optional): Path to blueprint file to define Pangolin resources and configurations.
- `no-cloud` (optional): Don't fail over to the cloud when using managed nodes in Pangolin Cloud. Default: false
## Environment Variables ## Environment Variables
@@ -87,6 +89,8 @@ All CLI arguments can be set using environment variables as an alternative to co
- `INTERFACE`: Name of the WireGuard interface. Default: newt (equivalent to `--interface`) - `INTERFACE`: Name of the WireGuard interface. Default: newt (equivalent to `--interface`)
- `KEEP_INTERFACE`: Keep the WireGuard interface after shutdown. Default: false (equivalent to `--keep-interface`) - `KEEP_INTERFACE`: Keep the WireGuard interface after shutdown. Default: false (equivalent to `--keep-interface`)
- `CONFIG_FILE`: Load the config json from this file instead of in the home folder. - `CONFIG_FILE`: Load the config json from this file instead of in the home folder.
- `BLUEPRINT_FILE`: Path to blueprint file to define Pangolin resources and configurations. (equivalent to `--blueprint-file`)
- `NO_CLOUD`: Don't fail over to the cloud when using managed nodes in Pangolin Cloud. Default: false (equivalent to `--no-cloud`)
## Loading secrets from files ## Loading secrets from files

37
blueprint.yaml Normal file
View File

@@ -0,0 +1,37 @@
resources:
resource-nice-id:
name: this is my resource
protocol: http
full-domain: level1.test3.example.com
host-header: example.com
tls-server-name: example.com
auth:
pincode: 123456
password: sadfasdfadsf
sso-enabled: true
sso-roles:
- Member
sso-users:
- owen@fossorial.io
whitelist-users:
- owen@fossorial.io
targets:
# - site: glossy-plains-viscacha-rat
- hostname: localhost
method: http
port: 8000
healthcheck:
port: 8000
hostname: localhost
# - site: glossy-plains-viscacha-rat
- hostname: localhost
method: http
port: 8001
resource-nice-id2:
name: this is other resource
protocol: tcp
proxy-port: 3000
targets:
# - site: glossy-plains-viscacha-rat
- hostname: localhost
port: 3000

View File

@@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/fosrl/newt/logger" "github.com/fosrl/newt/logger"
@@ -321,3 +322,128 @@ func getHostContainer(dockerContext context.Context, dockerClient *client.Client
return &hostContainer, nil return &hostContainer, nil
} }
// EventCallback defines the function signature for handling Docker events
type EventCallback func(containers []Container)
// EventMonitor handles Docker event monitoring
type EventMonitor struct {
client *client.Client
ctx context.Context
cancel context.CancelFunc
callback EventCallback
socketPath string
enforceNetworkValidation bool
}
// NewEventMonitor creates a new Docker event monitor
func NewEventMonitor(socketPath string, enforceNetworkValidation bool, callback EventCallback) (*EventMonitor, error) {
if socketPath == "" {
socketPath = "unix:///var/run/docker.sock"
}
if !strings.Contains(socketPath, "://") {
socketPath = "unix://" + socketPath
}
cli, err := client.NewClientWithOpts(
client.WithHost(socketPath),
client.WithAPIVersionNegotiation(),
)
if err != nil {
return nil, fmt.Errorf("failed to create Docker client: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
return &EventMonitor{
client: cli,
ctx: ctx,
cancel: cancel,
callback: callback,
socketPath: socketPath,
enforceNetworkValidation: enforceNetworkValidation,
}, nil
}
// Start begins monitoring Docker events
func (em *EventMonitor) Start() error {
logger.Debug("Starting Docker event monitoring")
// Filter for container events we care about
eventFilters := filters.NewArgs()
eventFilters.Add("type", "container")
// eventFilters.Add("event", "create")
eventFilters.Add("event", "start")
eventFilters.Add("event", "stop")
// eventFilters.Add("event", "destroy")
// eventFilters.Add("event", "die")
// eventFilters.Add("event", "pause")
// eventFilters.Add("event", "unpause")
// Start listening for events
eventCh, errCh := em.client.Events(em.ctx, events.ListOptions{
Filters: eventFilters,
})
go func() {
defer func() {
if err := em.client.Close(); err != nil {
logger.Error("Error closing Docker client: %v", err)
}
}()
for {
select {
case event := <-eventCh:
logger.Debug("Docker event received: %s %s for container %s", event.Action, event.Type, event.Actor.ID[:12])
// Fetch updated container list and trigger callback
go em.handleEvent(event)
case err := <-errCh:
if err != nil && err != context.Canceled {
logger.Error("Docker event stream error: %v", err)
// Try to reconnect after a brief delay
time.Sleep(5 * time.Second)
if em.ctx.Err() == nil {
logger.Info("Attempting to reconnect to Docker event stream")
eventCh, errCh = em.client.Events(em.ctx, events.ListOptions{
Filters: eventFilters,
})
}
}
return
case <-em.ctx.Done():
logger.Info("Docker event monitoring stopped")
return
}
}
}()
return nil
}
// handleEvent processes a Docker event and triggers the callback with updated container list
func (em *EventMonitor) handleEvent(event events.Message) {
// Add a small delay to ensure Docker has fully processed the event
time.Sleep(100 * time.Millisecond)
containers, err := ListContainers(em.socketPath, em.enforceNetworkValidation)
if err != nil {
logger.Error("Failed to list containers after Docker event %s: %v", event.Action, err)
return
}
logger.Debug("Triggering callback with %d containers after Docker event %s", len(containers), event.Action)
em.callback(containers)
}
// Stop stops the event monitoring
func (em *EventMonitor) Stop() {
logger.Info("Stopping Docker event monitoring")
if em.cancel != nil {
em.cancel()
}
}

10
go.mod
View File

@@ -19,8 +19,10 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.38.0 go.opentelemetry.io/otel/sdk/metric v1.38.0
golang.org/x/crypto v0.43.0 golang.org/x/crypto v0.43.0
golang.org/x/net v0.45.0 golang.org/x/net v0.45.0
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
gopkg.in/yaml.v3 v3.0.1
google.golang.org/grpc v1.76.0 google.golang.org/grpc v1.76.0
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
software.sslmate.com/src/go-pkcs12 v0.6.0 software.sslmate.com/src/go-pkcs12 v0.6.0
@@ -39,7 +41,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
@@ -71,8 +73,12 @@ require (
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.37.0 // indirect golang.org/x/tools v0.37.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect

14
go.sum
View File

@@ -24,6 +24,8 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -33,8 +35,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
@@ -145,6 +147,8 @@ golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
@@ -161,11 +165,12 @@ golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
@@ -183,6 +188,7 @@ google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -76,7 +76,7 @@ type Monitor struct {
// NewMonitor creates a new health check monitor // NewMonitor creates a new health check monitor
func NewMonitor(callback StatusChangeCallback, enforceCert bool) *Monitor { func NewMonitor(callback StatusChangeCallback, enforceCert bool) *Monitor {
logger.Info("Creating new health check monitor with certificate enforcement: %t", enforceCert) logger.Debug("Creating new health check monitor with certificate enforcement: %t", enforceCert)
// Configure TLS settings based on certificate enforcement // Configure TLS settings based on certificate enforcement
transport := &http.Transport{ transport := &http.Transport{

1
key Normal file
View File

@@ -0,0 +1 @@
oBvcoMJZXGzTZ4X+aNSCCQIjroREFBeRCs+a328xWGA=

106
main.go
View File

@@ -79,6 +79,11 @@ type ExitNodePingResult struct {
WasPreviouslyConnected bool `json:"wasPreviouslyConnected"` WasPreviouslyConnected bool `json:"wasPreviouslyConnected"`
} }
type BlueprintResult struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
}
// Custom flag type for multiple CA files // Custom flag type for multiple CA files
type stringSlice []string type stringSlice []string
@@ -137,6 +142,8 @@ var (
adminAddr string adminAddr string
region string region string
metricsAsyncBytes bool metricsAsyncBytes bool
blueprintFile string
noCloud bool
// New mTLS configuration variables // New mTLS configuration variables
tlsClientCert string tlsClientCert string
@@ -175,10 +182,12 @@ func main() {
asyncBytesEnv := os.Getenv("NEWT_METRICS_ASYNC_BYTES") asyncBytesEnv := os.Getenv("NEWT_METRICS_ASYNC_BYTES")
keepInterface = keepInterfaceEnv == "true" keepInterface = keepInterfaceEnv == "true"
acceptClientsEnv := os.Getenv("ACCEPT_CLIENTS")
acceptClients = acceptClientsEnv == "true" acceptClients = acceptClientsEnv == "true"
useNativeInterfaceEnv := os.Getenv("USE_NATIVE_INTERFACE")
useNativeInterface = useNativeInterfaceEnv == "true" useNativeInterface = useNativeInterfaceEnv == "true"
enforceHealthcheckCertEnv := os.Getenv("ENFORCE_HC_CERT")
enforceHealthcheckCert = enforceHealthcheckCertEnv == "true" enforceHealthcheckCert = enforceHealthcheckCertEnv == "true"
dockerSocket = os.Getenv("DOCKER_SOCKET") dockerSocket = os.Getenv("DOCKER_SOCKET")
pingIntervalStr := os.Getenv("PING_INTERVAL") pingIntervalStr := os.Getenv("PING_INTERVAL")
pingTimeoutStr := os.Getenv("PING_TIMEOUT") pingTimeoutStr := os.Getenv("PING_TIMEOUT")
@@ -202,9 +211,12 @@ func main() {
// Legacy PKCS12 support (deprecated) // Legacy PKCS12 support (deprecated)
tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT_PKCS12") tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT_PKCS12")
// Keep backward compatibility with old environment variable name // Keep backward compatibility with old environment variable name
if tlsPrivateKey == "" { if tlsPrivateKey == "" && tlsClientKey == "" && len(tlsClientCAs) == 0 {
tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT") tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT")
} }
blueprintFile = os.Getenv("BLUEPRINT_FILE")
noCloudEnv := os.Getenv("NO_CLOUD")
noCloud = noCloudEnv == "true"
if endpoint == "" { if endpoint == "" {
flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server") flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server")
@@ -304,6 +316,12 @@ func main() {
if healthFile == "" { if healthFile == "" {
flag.StringVar(&healthFile, "health-file", "", "Path to health file (if unset, health file won't be written)") flag.StringVar(&healthFile, "health-file", "", "Path to health file (if unset, health file won't be written)")
} }
if blueprintFile == "" {
flag.StringVar(&blueprintFile, "blueprint-file", "", "Path to blueprint file (if unset, no blueprint will be applied)")
}
if noCloudEnv == "" {
flag.BoolVar(&noCloud, "no-cloud", false, "Disable cloud failover")
}
// Metrics/observability flags (mirror ENV if unset) // Metrics/observability flags (mirror ENV if unset)
if metricsEnabledEnv == "" { if metricsEnabledEnv == "" {
@@ -520,6 +538,7 @@ func main() {
var pm *proxy.ProxyManager var pm *proxy.ProxyManager
var connected bool var connected bool
var wgData WgData var wgData WgData
var dockerEventMonitor *docker.EventMonitor
if acceptClients { if acceptClients {
setupClients(client) setupClients(client)
@@ -585,7 +604,7 @@ func main() {
// Register handlers for different message types // Register handlers for different message types
client.RegisterHandler("newt/wg/connect", func(msg websocket.WSMessage) { client.RegisterHandler("newt/wg/connect", func(msg websocket.WSMessage) {
logger.Info("Received registration message") logger.Debug("Received registration message")
regResult := "success" regResult := "success"
defer func() { defer func() {
telemetry.IncSiteRegistration(ctx, regResult) telemetry.IncSiteRegistration(ctx, regResult)
@@ -693,7 +712,7 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
logger.Warn("Initial reliable ping failed, but continuing: %v", err) logger.Warn("Initial reliable ping failed, but continuing: %v", err)
regResult = "failure" regResult = "failure"
} else { } else {
logger.Info("Initial connection test successful") logger.Debug("Initial connection test successful")
} }
pingWithRetryStopChan, _ = pingWithRetry(tnet, wgData.ServerIP, pingTimeout) pingWithRetryStopChan, _ = pingWithRetry(tnet, wgData.ServerIP, pingTimeout)
@@ -735,7 +754,7 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
if err := healthMonitor.AddTargets(wgData.HealthCheckTargets); err != nil { if err := healthMonitor.AddTargets(wgData.HealthCheckTargets); err != nil {
logger.Error("Failed to bulk add health check targets: %v", err) logger.Error("Failed to bulk add health check targets: %v", err)
} else { } else {
logger.Info("Successfully added %d health check targets", len(wgData.HealthCheckTargets)) logger.Debug("Successfully added %d health check targets", len(wgData.HealthCheckTargets))
} }
err = pm.Start() err = pm.Start()
@@ -768,7 +787,9 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
} }
// Request exit nodes from the server // Request exit nodes from the server
stopFunc = client.SendMessageInterval("newt/ping/request", map[string]interface{}{}, 3*time.Second) stopFunc = client.SendMessageInterval("newt/ping/request", map[string]interface{}{
"noCloud": noCloud,
}, 3*time.Second)
logger.Info("Tunnel destroyed, ready for reconnection") logger.Info("Tunnel destroyed, ready for reconnection")
}) })
@@ -794,7 +815,7 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
}) })
client.RegisterHandler("newt/ping/exitNodes", func(msg websocket.WSMessage) { client.RegisterHandler("newt/ping/exitNodes", func(msg websocket.WSMessage) {
logger.Info("Received ping message") logger.Debug("Received ping message")
if stopFunc != nil { if stopFunc != nil {
stopFunc() // stop the ws from sending more requests stopFunc() // stop the ws from sending more requests
stopFunc = nil // reset stopFunc to nil to avoid double stopping stopFunc = nil // reset stopFunc to nil to avoid double stopping
@@ -1085,7 +1106,7 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
if err != nil { if err != nil {
logger.Error("Failed to send Docker socket check response: %v", err) logger.Error("Failed to send Docker socket check response: %v", err)
} else { } else {
logger.Info("Docker socket check response sent: available=%t", isAvailable) logger.Debug("Docker socket check response sent: available=%t", isAvailable)
} }
}) })
@@ -1116,7 +1137,7 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
if err != nil { if err != nil {
logger.Error("Failed to send Docker container list: %v", err) logger.Error("Failed to send Docker container list: %v", err)
} else { } else {
logger.Info("Docker container list sent, count: %d", len(containers)) logger.Debug("Docker container list sent, count: %d", len(containers))
} }
}) })
@@ -1232,7 +1253,7 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
if err := healthMonitor.AddTargets(config.Targets); err != nil { if err := healthMonitor.AddTargets(config.Targets); err != nil {
logger.Error("Failed to add health check targets: %v", err) logger.Error("Failed to add health check targets: %v", err)
} else { } else {
logger.Info("Added %d health check targets", len(config.Targets)) logger.Debug("Added %d health check targets", len(config.Targets))
} }
logger.Debug("Health check targets added: %+v", config.Targets) logger.Debug("Health check targets added: %+v", config.Targets)
@@ -1340,6 +1361,29 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
} }
}) })
// Register handler for getting health check status
client.RegisterHandler("newt/blueprint/results", func(msg websocket.WSMessage) {
logger.Debug("Received blueprint results message")
var blueprintResult BlueprintResult
jsonData, err := json.Marshal(msg.Data)
if err != nil {
logger.Info("Error marshaling data: %v", err)
return
}
if err := json.Unmarshal(jsonData, &blueprintResult); err != nil {
logger.Info("Error unmarshaling config results data: %v", err)
return
}
if blueprintResult.Success {
logger.Debug("Blueprint applied successfully!")
} else {
logger.Warn("Blueprint application failed: %s", blueprintResult.Message)
}
})
client.OnConnect(func() error { client.OnConnect(func() error {
publicKey = privateKey.PublicKey() publicKey = privateKey.PublicKey()
logger.Debug("Public key: %s", publicKey) logger.Debug("Public key: %s", publicKey)
@@ -1350,9 +1394,11 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
if stopFunc != nil { if stopFunc != nil {
stopFunc() stopFunc()
} }
// request from the server the list of nodes to ping at newt/ping/request // request from the server the list of nodes to ping
stopFunc = client.SendMessageInterval("newt/ping/request", map[string]interface{}{}, 3*time.Second) stopFunc = client.SendMessageInterval("newt/ping/request", map[string]interface{}{
logger.Info("Requesting exit nodes from server") "noCloud": noCloud,
}, 3*time.Second)
logger.Debug("Requesting exit nodes from server")
clientsOnConnect() clientsOnConnect()
} }
@@ -1363,6 +1409,8 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
"backwardsCompatible": true, "backwardsCompatible": true,
}) })
sendBlueprint(client)
if err != nil { if err != nil {
logger.Error("Failed to send registration message: %v", err) logger.Error("Failed to send registration message: %v", err)
return err return err
@@ -1377,6 +1425,34 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
} }
defer client.Close() defer client.Close()
// Initialize Docker event monitoring if Docker socket is available and monitoring is enabled
if dockerSocket != "" {
logger.Debug("Initializing Docker event monitoring")
dockerEventMonitor, err = docker.NewEventMonitor(dockerSocket, dockerEnforceNetworkValidationBool, func(containers []docker.Container) {
// Send updated container list via websocket when Docker events occur
logger.Debug("Docker event detected, sending updated container list (%d containers)", len(containers))
err := client.SendMessage("newt/socket/containers", map[string]interface{}{
"containers": containers,
})
if err != nil {
logger.Error("Failed to send updated container list after Docker event: %v", err)
} else {
logger.Debug("Updated container list sent successfully")
}
})
if err != nil {
logger.Error("Failed to create Docker event monitor: %v", err)
} else {
err = dockerEventMonitor.Start()
if err != nil {
logger.Error("Failed to start Docker event monitoring: %v", err)
} else {
logger.Debug("Docker event monitoring started successfully")
}
}
}
// Wait for interrupt signal // Wait for interrupt signal
sigCh := make(chan os.Signal, 1) sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
@@ -1385,6 +1461,10 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
// Close clients first (including WGTester) // Close clients first (including WGTester)
closeClients() closeClients()
if dockerEventMonitor != nil {
dockerEventMonitor.Stop()
}
if healthMonitor != nil { if healthMonitor != nil {
healthMonitor.Stop() healthMonitor.Stop()
} }

45
util.go
View File

@@ -23,6 +23,7 @@ import (
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun/netstack" "golang.zx2c4.com/wireguard/tun/netstack"
"gopkg.in/yaml.v3"
) )
const msgHealthFileWriteFailed = "Failed to write health file: %v" const msgHealthFileWriteFailed = "Failed to write health file: %v"
@@ -574,3 +575,47 @@ func executeUpdownScript(action, proto, target string) (string, error) {
return target, nil return target, nil
} }
func sendBlueprint(client *websocket.Client) error {
if blueprintFile == "" {
return nil
}
// try to read the blueprint file
blueprintData, err := os.ReadFile(blueprintFile)
if err != nil {
logger.Error("Failed to read blueprint file: %v", err)
} else {
// first we should convert the yaml to json and error if the yaml is bad
var yamlObj interface{}
var blueprintJsonData string
err = yaml.Unmarshal(blueprintData, &yamlObj)
if err != nil {
logger.Error("Failed to parse blueprint YAML: %v", err)
} else {
// convert to json
jsonBytes, err := json.Marshal(yamlObj)
if err != nil {
logger.Error("Failed to convert blueprint to JSON: %v", err)
} else {
blueprintJsonData = string(jsonBytes)
logger.Debug("Converted blueprint to JSON: %s", blueprintJsonData)
}
}
// if we have valid json data, we can send it to the server
if blueprintJsonData == "" {
logger.Error("No valid blueprint JSON data to send to server")
return nil
}
logger.Info("Sending blueprint to server for application")
// send the blueprint data to the server
err = client.SendMessage("newt/blueprint/apply", map[string]interface{}{
"blueprint": blueprintJsonData,
})
}
return nil
}

View File

@@ -45,6 +45,7 @@ type Client struct {
tlsConfig TLSConfig tlsConfig TLSConfig
metricsCtxMu sync.RWMutex metricsCtxMu sync.RWMutex
metricsCtx context.Context metricsCtx context.Context
configNeedsSave bool // Flag to track if config needs to be saved
} }
type ClientOption func(*Client) type ClientOption func(*Client)

View File

@@ -6,6 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"github.com/fosrl/newt/logger"
) )
func getConfigPath(clientType string) string { func getConfigPath(clientType string) string {
@@ -33,14 +35,25 @@ func getConfigPath(clientType string) string {
} }
func (c *Client) loadConfig() error { func (c *Client) loadConfig() error {
originalConfig := *c.config // Store original config to detect changes
configPath := getConfigPath(c.clientType)
if c.config.ID != "" && c.config.Secret != "" && c.config.Endpoint != "" { if c.config.ID != "" && c.config.Secret != "" && c.config.Endpoint != "" {
logger.Debug("Config already provided, skipping loading from file")
// Check if config file exists, if not, we should save it
if _, err := os.Stat(configPath); os.IsNotExist(err) {
logger.Info("Config file does not exist at %s, will create it", configPath)
c.configNeedsSave = true
}
return nil return nil
} }
configPath := getConfigPath(c.clientType) logger.Info("Loading config from: %s", configPath)
data, err := os.ReadFile(configPath) data, err := os.ReadFile(configPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
logger.Info("Config file does not exist at %s, will create it with provided values", configPath)
c.configNeedsSave = true
return nil return nil
} }
return err return err
@@ -51,6 +64,12 @@ func (c *Client) loadConfig() error {
return err return err
} }
// Track what was loaded from file vs provided by CLI
fileHadID := c.config.ID == ""
fileHadSecret := c.config.Secret == ""
fileHadCert := c.config.TlsClientCert == ""
fileHadEndpoint := c.config.Endpoint == ""
if c.config.ID == "" { if c.config.ID == "" {
c.config.ID = config.ID c.config.ID = config.ID
} }
@@ -65,14 +84,37 @@ func (c *Client) loadConfig() error {
c.baseURL = config.Endpoint c.baseURL = config.Endpoint
} }
// Check if CLI args provided values that override file values
if (!fileHadID && originalConfig.ID != "") ||
(!fileHadSecret && originalConfig.Secret != "") ||
(!fileHadCert && originalConfig.TlsClientCert != "") ||
(!fileHadEndpoint && originalConfig.Endpoint != "") {
logger.Info("CLI arguments provided, config will be updated")
c.configNeedsSave = true
}
logger.Debug("Loaded config from %s", configPath)
logger.Debug("Config: %+v", c.config)
return nil return nil
} }
func (c *Client) saveConfig() error { func (c *Client) saveConfig() error {
if !c.configNeedsSave {
logger.Debug("Config has not changed, skipping save")
return nil
}
configPath := getConfigPath(c.clientType) configPath := getConfigPath(c.clientType)
data, err := json.MarshalIndent(c.config, "", " ") data, err := json.MarshalIndent(c.config, "", " ")
if err != nil { if err != nil {
return err return err
} }
return os.WriteFile(configPath, data, 0644)
logger.Info("Saving config to: %s", configPath)
err = os.WriteFile(configPath, data, 0644)
if err == nil {
c.configNeedsSave = false // Reset flag after successful save
}
return err
} }

View File

@@ -156,6 +156,7 @@ func NewWireGuardService(interfaceName string, mtu int, generateAndSaveKeyTo str
} }
var key wgtypes.Key var key wgtypes.Key
var port uint16
// if generateAndSaveKeyTo is provided, generate a private key and save it to the file. if the file already exists, load the key from the file // if generateAndSaveKeyTo is provided, generate a private key and save it to the file. if the file already exists, load the key from the file
key, err = wgtypes.GeneratePrivateKey() key, err = wgtypes.GeneratePrivateKey()
if err != nil { if err != nil {
@@ -181,12 +182,36 @@ func NewWireGuardService(interfaceName string, mtu int, generateAndSaveKeyTo str
} }
} }
// Get the existing wireguard port
device, err := wgClient.Device(interfaceName)
if err == nil {
port = uint16(device.ListenPort)
// also set the private key to the existing key
key = device.PrivateKey
if port != 0 {
logger.Info("WireGuard interface %s already exists with port %d\n", interfaceName, port)
} else {
port, err = FindAvailableUDPPort(49152, 65535)
if err != nil {
fmt.Printf("Error finding available port: %v\n", err)
return nil, err
}
}
} else {
port, err = FindAvailableUDPPort(49152, 65535)
if err != nil {
fmt.Printf("Error finding available port: %v\n", err)
return nil, err
}
}
service := &WireGuardService{ service := &WireGuardService{
interfaceName: interfaceName, interfaceName: interfaceName,
mtu: mtu, mtu: mtu,
client: wsClient, client: wsClient,
wgClient: wgClient, wgClient: wgClient,
key: key, key: key,
Port: port,
keyFilePath: generateAndSaveKeyTo, keyFilePath: generateAndSaveKeyTo,
newtId: newtId, newtId: newtId,
host: host, host: host,
@@ -194,27 +219,6 @@ func NewWireGuardService(interfaceName string, mtu int, generateAndSaveKeyTo str
stopHolepunch: make(chan struct{}), stopHolepunch: make(chan struct{}),
} }
// Get the existing wireguard port (keep this part)
device, err := service.wgClient.Device(service.interfaceName)
if err == nil {
service.Port = uint16(device.ListenPort)
if service.Port != 0 {
logger.Info("WireGuard interface %s already exists with port %d\n", service.interfaceName, service.Port)
} else {
service.Port, err = FindAvailableUDPPort(49152, 65535)
if err != nil {
fmt.Printf("Error finding available port: %v\n", err)
return nil, err
}
}
} else {
service.Port, err = FindAvailableUDPPort(49152, 65535)
if err != nil {
fmt.Printf("Error finding available port: %v\n", err)
return nil, err
}
}
// Register websocket handlers // Register websocket handlers
wsClient.RegisterHandler("newt/wg/receive-config", service.handleConfig) wsClient.RegisterHandler("newt/wg/receive-config", service.handleConfig)
wsClient.RegisterHandler("newt/wg/peer/add", service.handleAddPeer) wsClient.RegisterHandler("newt/wg/peer/add", service.handleAddPeer)
@@ -979,22 +983,30 @@ func (s *WireGuardService) encryptPayload(payload []byte) (interface{}, error) {
} }
func (s *WireGuardService) keepSendingUDPHolePunch(host string) { func (s *WireGuardService) keepSendingUDPHolePunch(host string) {
logger.Info("Starting UDP hole punch routine to %s:21820", host)
// send initial hole punch // send initial hole punch
if err := s.sendUDPHolePunch(host + ":21820"); err != nil { if err := s.sendUDPHolePunch(host + ":21820"); err != nil {
logger.Error("Failed to send initial UDP hole punch: %v", err) logger.Debug("Failed to send initial UDP hole punch: %v", err)
} }
ticker := time.NewTicker(3 * time.Second) ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop() defer ticker.Stop()
timeout := time.NewTimer(15 * time.Second)
defer timeout.Stop()
for { for {
select { select {
case <-s.stopHolepunch: case <-s.stopHolepunch:
logger.Info("Stopping UDP holepunch") logger.Info("Stopping UDP holepunch")
return return
case <-timeout.C:
logger.Info("UDP holepunch routine timed out after 15 seconds")
return
case <-ticker.C: case <-ticker.C:
if err := s.sendUDPHolePunch(host + ":21820"); err != nil { if err := s.sendUDPHolePunch(host + ":21820"); err != nil {
logger.Error("Failed to send UDP hole punch: %v", err) logger.Debug("Failed to send UDP hole punch: %v", err)
} }
} }
} }

View File

@@ -190,6 +190,13 @@ func NewWireGuardService(interfaceName string, mtu int, generateAndSaveKeyTo str
// Load or generate private key // Load or generate private key
if generateAndSaveKeyTo != "" { if generateAndSaveKeyTo != "" {
if _, err := os.Stat(generateAndSaveKeyTo); os.IsNotExist(err) { if _, err := os.Stat(generateAndSaveKeyTo); os.IsNotExist(err) {
// File doesn't exist, save the generated key
err = os.WriteFile(generateAndSaveKeyTo, []byte(key.String()), 0600)
if err != nil {
return nil, fmt.Errorf("failed to save private key: %v", err)
}
} else {
// File exists, read the existing key
keyData, err := os.ReadFile(generateAndSaveKeyTo) keyData, err := os.ReadFile(generateAndSaveKeyTo)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read private key: %v", err) return nil, fmt.Errorf("failed to read private key: %v", err)
@@ -198,11 +205,6 @@ func NewWireGuardService(interfaceName string, mtu int, generateAndSaveKeyTo str
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse private key: %v", err) return nil, fmt.Errorf("failed to parse private key: %v", err)
} }
} else {
err = os.WriteFile(generateAndSaveKeyTo, []byte(key.String()), 0600)
if err != nil {
return nil, fmt.Errorf("failed to save private key: %v", err)
}
} }
} }
@@ -1083,11 +1085,17 @@ func (s *WireGuardService) keepSendingUDPHolePunch(host string) {
ticker := time.NewTicker(3 * time.Second) ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop() defer ticker.Stop()
timeout := time.NewTimer(15 * time.Second)
defer timeout.Stop()
for { for {
select { select {
case <-s.stopHolepunch: case <-s.stopHolepunch:
logger.Info("Stopping UDP holepunch") logger.Info("Stopping UDP holepunch")
return return
case <-timeout.C:
logger.Info("UDP holepunch routine timed out after 15 seconds")
return
case <-ticker.C: case <-ticker.C:
if err := s.sendUDPHolePunch(host + ":21820"); err != nil { if err := s.sendUDPHolePunch(host + ":21820"); err != nil {
logger.Debug("Failed to send UDP hole punch: %v", err) logger.Debug("Failed to send UDP hole punch: %v", err)