mirror of
https://github.com/h44z/wg-portal.git
synced 2026-04-09 17:06:28 +00:00
Compare commits
26 Commits
wg_check_i
...
v2.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95394628d3 | ||
|
|
b553375c43 | ||
|
|
0a8ec71b3f | ||
|
|
fe4485037a | ||
|
|
6e47d8c3e9 | ||
|
|
eb28492539 | ||
|
|
d1a4ddde10 | ||
|
|
b1637b0c4e | ||
|
|
0cc7ebb83e | ||
|
|
eb6a787cfc | ||
|
|
b546eec4ed | ||
|
|
9be2133220 | ||
|
|
b05837b2d9 | ||
|
|
08c8f8eac0 | ||
|
|
d864e24145 | ||
|
|
5b56e58fe9 | ||
|
|
930ef7b573 | ||
|
|
18296673d7 | ||
|
|
4ccc59c109 | ||
|
|
e6b01a9903 | ||
|
|
2f79dd04c0 | ||
|
|
e5ed9736b3 | ||
|
|
c8353b85ae | ||
|
|
6142031387 | ||
|
|
dd86d0ff49 | ||
|
|
bdd426a679 |
@@ -28,7 +28,6 @@ core:
|
||||
|
||||
backend:
|
||||
default: local
|
||||
rekey_timeout_interval: 125s
|
||||
local_resolvconf_prefix: tun.
|
||||
|
||||
advanced:
|
||||
@@ -204,13 +203,6 @@ The current MikroTik backend is in **BETA** and may not support all features.
|
||||
- **Description:** The default backend to use for managing WireGuard interfaces.
|
||||
Valid options are: `local`, or other backend id's configured in the `mikrotik` section.
|
||||
|
||||
### `rekey_timeout_interval`
|
||||
- **Default:** `180s`
|
||||
- **Environment Variable:** `WG_PORTAL_BACKEND_REKEY_TIMEOUT_INTERVAL`
|
||||
- **Description:** The interval after which a WireGuard peer is considered disconnected if no handshake updates are received.
|
||||
This corresponds to the WireGuard rekey timeout setting of 120 seconds plus a 60-second buffer to account for latency or retry handling.
|
||||
Uses Go duration format (e.g., `10s`, `1m`). If omitted, a default of 180 seconds is used.
|
||||
|
||||
### `local_resolvconf_prefix`
|
||||
- **Default:** `tun.`
|
||||
- **Environment Variable:** `WG_PORTAL_BACKEND_LOCAL_RESOLVCONF_PREFIX`
|
||||
|
||||
@@ -35,14 +35,6 @@ WireGuard Portal supports managing WireGuard interfaces through three distinct d
|
||||
> :warning: If host networking is used, the WireGuard Portal UI will be accessible on all the host's IP addresses if the listening address is set to `:8888` in the configuration file.
|
||||
To avoid this, you can bind the listening address to a specific IP address, for example, the loopback address (`127.0.0.1:8888`). It is also possible to deploy firewall rules to restrict access to the WireGuard Portal UI.
|
||||
|
||||
> :warning: If the host is running **systemd-networkd**, routes managed by WireGuard Portal may be removed whenever systemd-networkd restarts, as it will clean up routes it considers "foreign". To prevent this, add the following to your host's network configuration (e.g. `/etc/systemd/networkd.conf` or a drop-in file):
|
||||
> ```ini
|
||||
> [Network]
|
||||
> ManageForeignRoutingPolicyRules=no
|
||||
> ManageForeignRoutes=no
|
||||
> ```
|
||||
> After editing, reload the configuration with `sudo systemctl restart systemd-networkd`. For more information refer to the [systemd-networkd documentation](https://www.freedesktop.org/software/systemd/man/latest/networkd.conf.html#ManageForeignRoutes=).
|
||||
|
||||
- **Within the WireGuard Portal Docker container**:
|
||||
WireGuard interfaces can be managed directly from within the WireGuard Portal container itself.
|
||||
This is the recommended approach when running WireGuard Portal via Docker, as it encapsulates all functionality in a single, portable container without requiring a separate WireGuard host or image.
|
||||
|
||||
@@ -204,13 +204,13 @@ func (c *StatisticsCollector) collectPeerData(ctx context.Context) {
|
||||
|
||||
// calculate if session was restarted
|
||||
p.UpdatedAt = now
|
||||
p.LastSessionStart = c.getSessionStartTime(*p, peer.BytesUpload, peer.BytesDownload,
|
||||
p.LastSessionStart = getSessionStartTime(*p, peer.BytesUpload, peer.BytesDownload,
|
||||
lastHandshake)
|
||||
p.BytesReceived = peer.BytesUpload // store bytes that where uploaded from the peer and received by the server
|
||||
p.BytesTransmitted = peer.BytesDownload // store bytes that where received from the peer and sent by the server
|
||||
p.Endpoint = peer.Endpoint
|
||||
p.LastHandshake = lastHandshake
|
||||
p.CalcConnected(c.cfg.Backend.ReKeyTimeoutInterval)
|
||||
p.CalcConnected()
|
||||
|
||||
if wasConnected != p.IsConnected {
|
||||
slog.Debug("peer connection state changed",
|
||||
@@ -249,7 +249,7 @@ func (c *StatisticsCollector) collectPeerData(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StatisticsCollector) getSessionStartTime(
|
||||
func getSessionStartTime(
|
||||
oldStats domain.PeerStatus,
|
||||
newReceived, newTransmitted uint64,
|
||||
latestHandshake *time.Time,
|
||||
@@ -258,7 +258,7 @@ func (c *StatisticsCollector) getSessionStartTime(
|
||||
return nil // currently not connected
|
||||
}
|
||||
|
||||
oldestHandshakeTime := time.Now().Add(-1 * c.cfg.Backend.ReKeyTimeoutInterval) // if a handshake is older than the rekey interval + grace-period, the peer is no longer connected
|
||||
oldestHandshakeTime := time.Now().Add(-2 * time.Minute) // if a handshake is older than 2 minutes, the peer is no longer connected
|
||||
switch {
|
||||
// old session was never initiated
|
||||
case oldStats.BytesReceived == 0 && oldStats.BytesTransmitted == 0 && (newReceived > 0 || newTransmitted > 0):
|
||||
@@ -369,7 +369,7 @@ func (c *StatisticsCollector) pingWorker(ctx context.Context) {
|
||||
p.LastPing = nil
|
||||
}
|
||||
p.UpdatedAt = time.Now()
|
||||
p.CalcConnected(c.cfg.Backend.ReKeyTimeoutInterval)
|
||||
p.CalcConnected()
|
||||
|
||||
if wasConnected != p.IsConnected {
|
||||
connectionStateChanged = true
|
||||
|
||||
@@ -5,11 +5,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
func TestStatisticsCollector_getSessionStartTime(t *testing.T) {
|
||||
func Test_getSessionStartTime(t *testing.T) {
|
||||
now := time.Now()
|
||||
nowMinus1 := now.Add(-1 * time.Minute)
|
||||
nowMinus3 := now.Add(-3 * time.Minute)
|
||||
@@ -134,14 +133,7 @@ func TestStatisticsCollector_getSessionStartTime(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &StatisticsCollector{
|
||||
cfg: &config.Config{
|
||||
Backend: config.Backend{
|
||||
ReKeyTimeoutInterval: 180 * time.Second,
|
||||
},
|
||||
},
|
||||
}
|
||||
if got := c.getSessionStartTime(tt.args.oldStats, tt.args.newReceived, tt.args.newTransmitted,
|
||||
if got := getSessionStartTime(tt.args.oldStats, tt.args.newReceived, tt.args.newTransmitted,
|
||||
tt.args.lastHandshake); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getSessionStartTime() = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ const LocalBackendName = "local"
|
||||
type Backend struct {
|
||||
Default string `yaml:"default"` // The default backend to use (defaults to the internal backend)
|
||||
|
||||
ReKeyTimeoutInterval time.Duration `yaml:"rekey_timeout_interval"` // Interval after which a connection is assumed dead
|
||||
|
||||
// Local Backend-specific configuration
|
||||
|
||||
IgnoredLocalInterfaces []string `yaml:"ignored_local_interfaces"` // A list of interface names that should be ignored by this backend (e.g., "wg0")
|
||||
@@ -117,8 +115,8 @@ func (b *BackendMikrotik) GetApiTimeout() time.Duration {
|
||||
type BackendPfsense struct {
|
||||
BackendBase `yaml:",inline"` // Embed the base fields
|
||||
|
||||
ApiUrl string `yaml:"api_url"` // The base URL of the pfSense REST API (e.g., "https://pfsense.example.com/api/v2")
|
||||
ApiKey string `yaml:"api_key"` // API key for authentication (generated in pfSense under 'System' -> 'REST API' -> 'Keys')
|
||||
ApiUrl string `yaml:"api_url"` // The base URL of the pfSense REST API (e.g., "https://pfsense.example.com/api/v2")
|
||||
ApiKey string `yaml:"api_key"` // API key for authentication (generated in pfSense under 'System' -> 'REST API' -> 'Keys')
|
||||
ApiVerifyTls bool `yaml:"api_verify_tls"` // Whether to verify the TLS certificate of the pfSense API
|
||||
ApiTimeout time.Duration `yaml:"api_timeout"` // Timeout for API requests (default: 30 seconds)
|
||||
|
||||
|
||||
@@ -139,7 +139,6 @@ func defaultConfig() *Config {
|
||||
|
||||
cfg.Backend = Backend{
|
||||
Default: LocalBackendName, // local backend is the default (using wgcrtl)
|
||||
ReKeyTimeoutInterval: getEnvDuration("WG_PORTAL_BACKEND_REKEY_TIMEOUT_INTERVAL", 180*time.Second),
|
||||
IgnoredLocalInterfaces: getEnvStrSlice("WG_PORTAL_BACKEND_IGNORED_LOCAL_INTERFACES", nil),
|
||||
// Most resolconf implementations use "tun." as a prefix for interface names.
|
||||
// But systemd's implementation uses no prefix, for example.
|
||||
|
||||
@@ -21,8 +21,8 @@ type PeerStatus struct {
|
||||
LastSessionStart *time.Time `gorm:"column:last_session_start" json:"LastSessionStart"`
|
||||
}
|
||||
|
||||
func (s *PeerStatus) CalcConnected(timeout time.Duration) {
|
||||
oldestHandshakeTime := time.Now().Add(-1 * timeout) // if a handshake is older than the rekey-interval + grace-period, the peer is no longer connected
|
||||
func (s *PeerStatus) CalcConnected() {
|
||||
oldestHandshakeTime := time.Now().Add(-2 * time.Minute) // if a handshake is older than 2 minutes, the peer is no longer connected
|
||||
|
||||
handshakeValid := false
|
||||
if s.LastHandshake != nil {
|
||||
|
||||
@@ -9,16 +9,11 @@ func TestPeerStatus_IsConnected(t *testing.T) {
|
||||
now := time.Now()
|
||||
past := now.Add(-3 * time.Minute)
|
||||
recent := now.Add(-1 * time.Minute)
|
||||
defaultTimeout := 125 * time.Second // rekey interval of 120s + 5 seconds grace period
|
||||
past126 := now.Add(-1*defaultTimeout - 1*time.Second)
|
||||
past125 := now.Add(-1 * defaultTimeout)
|
||||
past124 := now.Add(-1*defaultTimeout + 1*time.Second)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
status PeerStatus
|
||||
timeout time.Duration
|
||||
want bool
|
||||
name string
|
||||
status PeerStatus
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Pingable and recent handshake",
|
||||
@@ -26,8 +21,7 @@ func TestPeerStatus_IsConnected(t *testing.T) {
|
||||
IsPingable: true,
|
||||
LastHandshake: &recent,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Not pingable but recent handshake",
|
||||
@@ -35,8 +29,7 @@ func TestPeerStatus_IsConnected(t *testing.T) {
|
||||
IsPingable: false,
|
||||
LastHandshake: &recent,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Pingable but old handshake",
|
||||
@@ -44,44 +37,15 @@ func TestPeerStatus_IsConnected(t *testing.T) {
|
||||
IsPingable: true,
|
||||
LastHandshake: &past,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Not pingable and ok handshake (-124s)",
|
||||
status: PeerStatus{
|
||||
IsPingable: false,
|
||||
LastHandshake: &past124,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Not pingable and old handshake (-125s)",
|
||||
status: PeerStatus{
|
||||
IsPingable: false,
|
||||
LastHandshake: &past125,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Not pingable and old handshake (-126s)",
|
||||
status: PeerStatus{
|
||||
IsPingable: false,
|
||||
LastHandshake: &past126,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Not pingable and old handshake (very old)",
|
||||
name: "Not pingable and old handshake",
|
||||
status: PeerStatus{
|
||||
IsPingable: false,
|
||||
LastHandshake: &past,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: false,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Pingable and no handshake",
|
||||
@@ -89,8 +53,7 @@ func TestPeerStatus_IsConnected(t *testing.T) {
|
||||
IsPingable: true,
|
||||
LastHandshake: nil,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Not pingable and no handshake",
|
||||
@@ -98,13 +61,12 @@ func TestPeerStatus_IsConnected(t *testing.T) {
|
||||
IsPingable: false,
|
||||
LastHandshake: nil,
|
||||
},
|
||||
timeout: defaultTimeout,
|
||||
want: false,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.status.CalcConnected(tt.timeout)
|
||||
tt.status.CalcConnected()
|
||||
if got := tt.status.IsConnected; got != tt.want {
|
||||
t.Errorf("IsConnected = %v, want %v", got, tt.want)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user