mirror of
https://github.com/fosrl/newt.git
synced 2026-03-26 20:46:41 +00:00
Merge branch 'main' into otel
This commit is contained in:
47
.github/DISCUSSION_TEMPLATE/feature-requests.yml
vendored
Normal file
47
.github/DISCUSSION_TEMPLATE/feature-requests.yml
vendored
Normal 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
51
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
Normal 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
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
||||||
2
.github/workflows/cicd.yml
vendored
2
.github/workflows/cicd.yml
vendored
@@ -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
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -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
|
||||||
|
|
||||||
|
|||||||
94
README.md
94
README.md
@@ -36,57 +36,61 @@ When Newt receives WireGuard control messages, it will use the information encod
|
|||||||
|
|
||||||
## CLI Args
|
## CLI Args
|
||||||
|
|
||||||
- `id`: Newt ID generated by Pangolin to identify the client.
|
- `id`: Newt ID generated by Pangolin to identify the client.
|
||||||
- `secret`: A unique secret (not shared and kept private) used to authenticate the client ID with the websocket in order to receive commands.
|
- `secret`: A unique secret (not shared and kept private) used to authenticate the client ID with the websocket in order to receive commands.
|
||||||
- `endpoint`: The endpoint where both Gerbil and Pangolin reside in order to connect to the websocket.
|
- `endpoint`: The endpoint where both Gerbil and Pangolin reside in order to connect to the websocket.
|
||||||
|
|
||||||
- `mtu` (optional): MTU for the internal WG interface. Default: 1280
|
- `mtu` (optional): MTU for the internal WG interface. Default: 1280
|
||||||
- `dns` (optional): DNS server to use to resolve the endpoint. Default: 9.9.9.9
|
- `dns` (optional): DNS server to use to resolve the endpoint. Default: 9.9.9.9
|
||||||
- `log-level` (optional): The log level to use (DEBUG, INFO, WARN, ERROR, FATAL). Default: INFO
|
- `log-level` (optional): The log level to use (DEBUG, INFO, WARN, ERROR, FATAL). Default: INFO
|
||||||
- `enforce-hc-cert` (optional): Enforce certificate validation for health checks. Default: false (accepts any cert)
|
- `enforce-hc-cert` (optional): Enforce certificate validation for health checks. Default: false (accepts any cert)
|
||||||
- `docker-socket` (optional): Set the Docker socket to use the container discovery integration
|
- `docker-socket` (optional): Set the Docker socket to use the container discovery integration
|
||||||
- `ping-interval` (optional): Interval for pinging the server. Default: 3s
|
- `ping-interval` (optional): Interval for pinging the server. Default: 3s
|
||||||
- `ping-timeout` (optional): Timeout for each ping. Default: 5s
|
- `ping-timeout` (optional): Timeout for each ping. Default: 5s
|
||||||
- `updown` (optional): A script to be called when targets are added or removed.
|
- `updown` (optional): A script to be called when targets are added or removed.
|
||||||
- `tls-client-cert` (optional): Client certificate (p12 or pfx) for mTLS. See [mTLS](#mtls)
|
- `tls-client-cert` (optional): Client certificate (p12 or pfx) for mTLS. See [mTLS](#mtls)
|
||||||
- `tls-client-cert` (optional): Path to client certificate (PEM format, optional if using PKCS12). See [mTLS](#mtls)
|
- `tls-client-cert` (optional): Path to client certificate (PEM format, optional if using PKCS12). See [mTLS](#mtls)
|
||||||
- `tls-client-key` (optional): Path to private key for mTLS (PEM format, optional if using PKCS12)
|
- `tls-client-key` (optional): Path to private key for mTLS (PEM format, optional if using PKCS12)
|
||||||
- `tls-ca-cert` (optional): Path to CA certificate to verify server (PEM format, optional if using PKCS12)
|
- `tls-ca-cert` (optional): Path to CA certificate to verify server (PEM format, optional if using PKCS12)
|
||||||
- `docker-enforce-network-validation` (optional): Validate the container target is on the same network as the newt process. Default: false
|
- `docker-enforce-network-validation` (optional): Validate the container target is on the same network as the newt process. Default: false
|
||||||
- `health-file` (optional): Check if connection to WG server (pangolin) is ok. creates a file if ok, removes it if not ok. Can be used with docker healtcheck to restart newt
|
- `health-file` (optional): Check if connection to WG server (pangolin) is ok. creates a file if ok, removes it if not ok. Can be used with docker healtcheck to restart newt
|
||||||
- `accept-clients` (optional): Enable WireGuard server mode to accept incoming newt client connections. Default: false
|
- `accept-clients` (optional): Enable WireGuard server mode to accept incoming newt client connections. Default: false
|
||||||
- `generateAndSaveKeyTo` (optional): Path to save generated private key
|
- `generateAndSaveKeyTo` (optional): Path to save generated private key
|
||||||
- `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
|
||||||
|
|
||||||
All CLI arguments can be set using environment variables as an alternative to command line flags. Environment variables are particularly useful when running Newt in containerized environments.
|
All CLI arguments can be set using environment variables as an alternative to command line flags. Environment variables are particularly useful when running Newt in containerized environments.
|
||||||
|
|
||||||
- `PANGOLIN_ENDPOINT`: Endpoint of your pangolin server (equivalent to `--endpoint`)
|
- `PANGOLIN_ENDPOINT`: Endpoint of your pangolin server (equivalent to `--endpoint`)
|
||||||
- `NEWT_ID`: Newt ID generated by Pangolin (equivalent to `--id`)
|
- `NEWT_ID`: Newt ID generated by Pangolin (equivalent to `--id`)
|
||||||
- `NEWT_SECRET`: Newt secret for authentication (equivalent to `--secret`)
|
- `NEWT_SECRET`: Newt secret for authentication (equivalent to `--secret`)
|
||||||
- `MTU`: MTU for the internal WG interface. Default: 1280 (equivalent to `--mtu`)
|
- `MTU`: MTU for the internal WG interface. Default: 1280 (equivalent to `--mtu`)
|
||||||
- `DNS`: DNS server to use to resolve the endpoint. Default: 9.9.9.9 (equivalent to `--dns`)
|
- `DNS`: DNS server to use to resolve the endpoint. Default: 9.9.9.9 (equivalent to `--dns`)
|
||||||
- `LOG_LEVEL`: Log level (DEBUG, INFO, WARN, ERROR, FATAL). Default: INFO (equivalent to `--log-level`)
|
- `LOG_LEVEL`: Log level (DEBUG, INFO, WARN, ERROR, FATAL). Default: INFO (equivalent to `--log-level`)
|
||||||
- `DOCKER_SOCKET`: Path to Docker socket for container discovery (equivalent to `--docker-socket`)
|
- `DOCKER_SOCKET`: Path to Docker socket for container discovery (equivalent to `--docker-socket`)
|
||||||
- `PING_INTERVAL`: Interval for pinging the server. Default: 3s (equivalent to `--ping-interval`)
|
- `PING_INTERVAL`: Interval for pinging the server. Default: 3s (equivalent to `--ping-interval`)
|
||||||
- `PING_TIMEOUT`: Timeout for each ping. Default: 5s (equivalent to `--ping-timeout`)
|
- `PING_TIMEOUT`: Timeout for each ping. Default: 5s (equivalent to `--ping-timeout`)
|
||||||
- `UPDOWN_SCRIPT`: Path to updown script for target add/remove events (equivalent to `--updown`)
|
- `UPDOWN_SCRIPT`: Path to updown script for target add/remove events (equivalent to `--updown`)
|
||||||
- `TLS_CLIENT_CERT`: Path to client certificate for mTLS (equivalent to `--tls-client-cert`)
|
- `TLS_CLIENT_CERT`: Path to client certificate for mTLS (equivalent to `--tls-client-cert`)
|
||||||
- `TLS_CLIENT_CERT`: Path to client certificate for mTLS (equivalent to `--tls-client-cert`)
|
- `TLS_CLIENT_CERT`: Path to client certificate for mTLS (equivalent to `--tls-client-cert`)
|
||||||
- `TLS_CLIENT_KEY`: Path to private key for mTLS (equivalent to `--tls-client-key`)
|
- `TLS_CLIENT_KEY`: Path to private key for mTLS (equivalent to `--tls-client-key`)
|
||||||
- `TLS_CA_CERT`: Path to CA certificate to verify server (equivalent to `--tls-ca-cert`)
|
- `TLS_CA_CERT`: Path to CA certificate to verify server (equivalent to `--tls-ca-cert`)
|
||||||
- `DOCKER_ENFORCE_NETWORK_VALIDATION`: Validate container targets are on same network. Default: false (equivalent to `--docker-enforce-network-validation`)
|
- `DOCKER_ENFORCE_NETWORK_VALIDATION`: Validate container targets are on same network. Default: false (equivalent to `--docker-enforce-network-validation`)
|
||||||
- `ENFORCE_HC_CERT`: Enforce certificate validation for health checks. Default: false (equivalent to `--enforce-hc-cert`)
|
- `ENFORCE_HC_CERT`: Enforce certificate validation for health checks. Default: false (equivalent to `--enforce-hc-cert`)
|
||||||
- `HEALTH_FILE`: Path to health file for connection monitoring (equivalent to `--health-file`)
|
- `HEALTH_FILE`: Path to health file for connection monitoring (equivalent to `--health-file`)
|
||||||
- `ACCEPT_CLIENTS`: Enable WireGuard server mode. Default: false (equivalent to `--accept-clients`)
|
- `ACCEPT_CLIENTS`: Enable WireGuard server mode. Default: false (equivalent to `--accept-clients`)
|
||||||
- `GENERATE_AND_SAVE_KEY_TO`: Path to save generated private key (equivalent to `--generateAndSaveKeyTo`)
|
- `GENERATE_AND_SAVE_KEY_TO`: Path to save generated private key (equivalent to `--generateAndSaveKeyTo`)
|
||||||
- `USE_NATIVE_INTERFACE`: Use native WireGuard interface (Linux only). Default: false (equivalent to `--native`)
|
- `USE_NATIVE_INTERFACE`: Use native WireGuard interface (Linux only). Default: false (equivalent to `--native`)
|
||||||
- `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
37
blueprint.yaml
Normal 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
|
||||||
126
docker/client.go
126
docker/client.go
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
12
go.mod
12
go.mod
@@ -19,9 +19,11 @@ 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
|
||||||
google.golang.org/grpc v1.76.0
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
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
14
go.sum
@@ -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=
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
106
main.go
106
main.go
@@ -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
45
util.go
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
56
wg/wg.go
56
wg/wg.go
@@ -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,40 +182,43 @@ func NewWireGuardService(interfaceName string, mtu int, generateAndSaveKeyTo str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
service := &WireGuardService{
|
// Get the existing wireguard port
|
||||||
interfaceName: interfaceName,
|
device, err := wgClient.Device(interfaceName)
|
||||||
mtu: mtu,
|
|
||||||
client: wsClient,
|
|
||||||
wgClient: wgClient,
|
|
||||||
key: key,
|
|
||||||
keyFilePath: generateAndSaveKeyTo,
|
|
||||||
newtId: newtId,
|
|
||||||
host: host,
|
|
||||||
lastReadings: make(map[string]PeerReading),
|
|
||||||
stopHolepunch: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the existing wireguard port (keep this part)
|
|
||||||
device, err := service.wgClient.Device(service.interfaceName)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
service.Port = uint16(device.ListenPort)
|
port = uint16(device.ListenPort)
|
||||||
if service.Port != 0 {
|
// also set the private key to the existing key
|
||||||
logger.Info("WireGuard interface %s already exists with port %d\n", service.interfaceName, service.Port)
|
key = device.PrivateKey
|
||||||
|
if port != 0 {
|
||||||
|
logger.Info("WireGuard interface %s already exists with port %d\n", interfaceName, port)
|
||||||
} else {
|
} else {
|
||||||
service.Port, err = FindAvailableUDPPort(49152, 65535)
|
port, err = FindAvailableUDPPort(49152, 65535)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error finding available port: %v\n", err)
|
fmt.Printf("Error finding available port: %v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
service.Port, err = FindAvailableUDPPort(49152, 65535)
|
port, err = FindAvailableUDPPort(49152, 65535)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error finding available port: %v\n", err)
|
fmt.Printf("Error finding available port: %v\n", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service := &WireGuardService{
|
||||||
|
interfaceName: interfaceName,
|
||||||
|
mtu: mtu,
|
||||||
|
client: wsClient,
|
||||||
|
wgClient: wgClient,
|
||||||
|
key: key,
|
||||||
|
Port: port,
|
||||||
|
keyFilePath: generateAndSaveKeyTo,
|
||||||
|
newtId: newtId,
|
||||||
|
host: host,
|
||||||
|
lastReadings: make(map[string]PeerReading),
|
||||||
|
stopHolepunch: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user