Compare commits

...

12 Commits
1.1.1 ... 1.1.2

Author SHA1 Message Date
Owen Schwartz
00e8050949 Fix pulling config.json (#39)
Former-commit-id: 64e7a20915
2025-09-26 09:37:13 -07:00
Owen Schwartz
8fa2da00b6 Merge pull request #30 from fosrl/dependabot/github_actions/actions/setup-go-6
Bump actions/setup-go from 5 to 6

Former-commit-id: 35d4f19bd8
2025-09-20 11:46:10 -04:00
Owen Schwartz
b851cd73c9 Merge pull request #34 from fosrl/dependabot/go_modules/golang.org/x/sys-0.36.0
Bump golang.org/x/sys from 0.35.0 to 0.36.0

Former-commit-id: a631b60604
2025-09-20 11:46:04 -04:00
Owen Schwartz
4c19d7ef6d Merge pull request #35 from fosrl/dependabot/go_modules/golang.org/x/crypto-0.42.0
Bump golang.org/x/crypto from 0.41.0 to 0.42.0

Former-commit-id: 1eb87c997d
2025-09-20 11:45:57 -04:00
Owen Schwartz
cbecb9a0ce Merge pull request #28 from kevin-gillet/126-stop-litteral-ipv6-from-being-resolved
fix: holepunch to only active peers and stop litteral ipv6 from being name resolved
Former-commit-id: 5d42fac1d1
2025-09-20 11:45:47 -04:00
dependabot[bot]
4fc8db08ba Bump golang.org/x/crypto from 0.41.0 to 0.42.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.41.0 to 0.42.0.
- [Commits](https://github.com/golang/crypto/compare/v0.41.0...v0.42.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Former-commit-id: 6d9d012789
2025-09-19 20:20:13 +00:00
dependabot[bot]
7ca46e0a75 Bump golang.org/x/sys from 0.35.0 to 0.36.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/sys/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Former-commit-id: 10b8ebd3c1
2025-09-19 20:20:10 +00:00
dependabot[bot]
a4ea5143af Bump actions/setup-go from 5 to 6
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Former-commit-id: f9d51ebb88
2025-09-08 20:44:23 +00:00
FranceNuage
e9257b6423 fix: holepunch to only active peers and stop litteral ipv6 from being treated as hostname and be name resolved
Former-commit-id: 2b41d4c459
2025-09-06 02:39:43 +02:00
Owen Schwartz
3c9d3a1d2c Merge pull request #26 from kevin-gillet/25-fix-olm-ipv6-parsing
fix: add ipv6 endpoint formatter
Former-commit-id: 7448a3127d
2025-09-04 10:37:40 -07:00
FranceNuage
b426f14190 fix: remove comment
Former-commit-id: e669d543c4
2025-09-04 14:16:41 +02:00
FranceNuage
d48acfba39 fix: add ipv6 endpoint formatter
Former-commit-id: 5b443a41a3
2025-09-04 14:09:58 +02:00
6 changed files with 110 additions and 157 deletions

View File

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

View File

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

6
go.mod
View File

@@ -5,9 +5,9 @@ go 1.25
require (
github.com/fosrl/newt v0.0.0-20250730062419-3ccd755d557a
github.com/vishvananda/netlink v1.3.1
golang.org/x/crypto v0.41.0
golang.org/x/crypto v0.42.0
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
golang.org/x/sys v0.35.0
golang.org/x/sys v0.36.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
)
@@ -15,7 +15,7 @@ require (
require (
github.com/gorilla/websocket v1.5.3 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 // indirect
software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect

12
go.sum
View File

@@ -10,16 +10,16 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=

236
main.go
View File

@@ -10,6 +10,7 @@ import (
"os/signal"
"runtime"
"strconv"
"strings"
"syscall"
"time"
@@ -25,6 +26,34 @@ import (
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// Helper function to format endpoints correctly
func formatEndpoint(endpoint string) string {
if endpoint == "" {
return ""
}
// Check if it's already a valid host:port that SplitHostPort can parse (e.g., [::1]:8080 or 1.2.3.4:8080)
_, _, err := net.SplitHostPort(endpoint)
if err == nil {
return endpoint // Already valid, no change needed
}
// If it failed, it might be our malformed "ipv6:port" string. Let's check and fix it.
lastColon := strings.LastIndex(endpoint, ":")
if lastColon > 0 { // Ensure there is a colon and it's not the first character
hostPart := endpoint[:lastColon]
// Check if the host part is a literal IPv6 address
if ip := net.ParseIP(hostPart); ip != nil && ip.To4() == nil {
// It is! Reformat it with brackets.
portPart := endpoint[lastColon+1:]
return fmt.Sprintf("[%s]:%s", hostPart, portPart)
}
}
// If it's not the specific malformed case, return it as is.
return endpoint
}
func main() {
// Check if we're running as a Windows service
if isWindowsService() {
@@ -34,8 +63,15 @@ func main() {
}
// Handle service management commands on Windows
if runtime.GOOS == "windows" && len(os.Args) > 1 {
switch os.Args[1] {
if runtime.GOOS == "windows" {
var command string
if len(os.Args) > 1 {
command = os.Args[1]
} else {
command = "default"
}
switch command {
case "install":
err := installService()
if err != nil {
@@ -118,6 +154,7 @@ func main() {
fmt.Println(" stop Stop the service")
fmt.Println(" status Show service status")
fmt.Println(" debug Run service in debug mode")
fmt.Println(" logs Tail the service log file")
fmt.Println("\nFor console mode, run without arguments or with standard flags.")
return
default:
@@ -373,6 +410,22 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
// }
// }
// Create a new olm
olm, err := websocket.NewClient(
"olm",
id, // CLI arg takes precedence
secret, // CLI arg takes precedence
endpoint,
pingInterval,
pingTimeout,
)
if err != nil {
logger.Fatal("Failed to create olm: %v", err)
}
endpoint = olm.GetConfig().Endpoint // Update endpoint from config
id = olm.GetConfig().ID // Update ID from config
secret = olm.GetConfig().Secret // Update secret from config
// wait until we have a client id and secret and endpoint
waitCount := 0
for id == "" || secret == "" || endpoint == "" {
@@ -410,21 +463,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
logger.Fatal("Failed to generate private key: %v", err)
}
// Create a new olm
olm, err := websocket.NewClient(
"olm",
id, // CLI arg takes precedence
secret, // CLI arg takes precedence
endpoint,
pingInterval,
pingTimeout,
)
if err != nil {
logger.Fatal("Failed to create olm: %v", err)
}
endpoint = olm.GetConfig().Endpoint // Update endpoint from config
id = olm.GetConfig().ID // Update ID from config
// Create TUN device and network stack
var dev *device.Device
var wgData WgData
@@ -498,29 +536,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
go keepSendingUDPHolePunch(legacyHolePunchData.Endpoint, id, sourcePort, legacyHolePunchData.ServerPubKey)
})
olm.RegisterHandler("olm/wg/holepunch/all", func(msg websocket.WSMessage) {
logger.Debug("Received message: %v", msg.Data)
jsonData, err := json.Marshal(msg.Data)
if err != nil {
logger.Info("Error marshaling data: %v", err)
return
}
if err := json.Unmarshal(jsonData, &holePunchData); err != nil {
logger.Info("Error unmarshaling target data: %v", err)
return
}
// Create a new stopHolepunch channel for the new set of goroutines
stopHolepunch = make(chan struct{})
// Start a single hole punch goroutine for all exit nodes
logger.Info("Starting hole punch for %d exit nodes", len(holePunchData.ExitNodes))
go keepSendingUDPHolePunchToMultipleExitNodes(holePunchData.ExitNodes, id, sourcePort)
})
// Register handlers for different message types
olm.RegisterHandler("olm/wg/connect", func(msg websocket.WSMessage) {
logger.Debug("Received message: %v", msg.Data)
@@ -558,9 +573,6 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
}
tdev, err = func() (tun.Device, error) {
tunFdStr := os.Getenv(ENV_WG_TUN_FD)
// if on macOS, call findUnusedUTUN to get a new utun device
if runtime.GOOS == "darwin" {
interfaceName, err := findUnusedUTUN()
if err != nil {
@@ -568,12 +580,10 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
}
return tun.CreateTUN(interfaceName, mtuInt)
}
if tunFdStr == "" {
return tun.CreateTUN(interfaceName, mtuInt)
if tunFdStr := os.Getenv(ENV_WG_TUN_FD); tunFdStr != "" {
return createTUNFromFD(tunFdStr, mtuInt)
}
return createTUNFromFD(tunFdStr, mtuInt)
return tun.CreateTUN(interfaceName, mtuInt)
}()
if err != nil {
@@ -581,75 +591,37 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
return
}
realInterfaceName, err2 := tdev.Name()
if err2 == nil {
if realInterfaceName, err2 := tdev.Name(); err2 == nil {
interfaceName = realInterfaceName
}
// open UAPI file (or use supplied fd)
fileUAPI, err := func() (*os.File, error) {
uapiFdStr := os.Getenv(ENV_WG_UAPI_FD)
if uapiFdStr == "" {
return uapiOpen(interfaceName)
if uapiFdStr := os.Getenv(ENV_WG_UAPI_FD); uapiFdStr != "" {
fd, err := strconv.ParseUint(uapiFdStr, 10, 32)
if err != nil { return nil, err }
return os.NewFile(uintptr(fd), ""), nil
}
// use supplied fd
fd, err := strconv.ParseUint(uapiFdStr, 10, 32)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(fd), ""), nil
return uapiOpen(interfaceName)
}()
if err != nil {
logger.Error("UAPI listen error: %v", err)
os.Exit(1)
return
}
dev = device.NewDevice(tdev, NewFixedPortBind(uint16(sourcePort)), device.NewLogger(
mapToWireGuardLogLevel(loggerLevel),
"wireguard: ",
))
errs := make(chan error)
if err != nil { logger.Error("UAPI listen error: %v", err); os.Exit(1); return }
dev = device.NewDevice(tdev, NewFixedPortBind(uint16(sourcePort)), device.NewLogger(mapToWireGuardLogLevel(loggerLevel), "wireguard: "))
uapiListener, err = uapiListen(interfaceName, fileUAPI)
if err != nil {
logger.Error("Failed to listen on uapi socket: %v", err)
os.Exit(1)
}
if err != nil { logger.Error("Failed to listen on uapi socket: %v", err); os.Exit(1) }
go func() {
for {
conn, err := uapiListener.Accept()
if err != nil {
errs <- err
return
}
if err != nil { return }
go dev.IpcHandle(conn)
}
}()
logger.Info("UAPI listener started")
// Bring up the device
err = dev.Up()
if err != nil {
logger.Error("Failed to bring up WireGuard device: %v", err)
}
// configure the interface
err = ConfigureInterface(realInterfaceName, wgData)
if err != nil {
logger.Error("Failed to configure interface: %v", err)
}
// Set tunnel IP in HTTP server
if httpServer != nil {
httpServer.SetTunnelIP(wgData.TunnelIP)
}
if err = dev.Up(); err != nil { logger.Error("Failed to bring up WireGuard device: %v", err) }
if err = ConfigureInterface(interfaceName, wgData); err != nil { logger.Error("Failed to configure interface: %v", err) }
if httpServer != nil { httpServer.SetTunnelIP(wgData.TunnelIP) }
peerMonitor = peermonitor.NewPeerMonitor(
func(siteID int, connected bool, rtt time.Duration) {
@@ -680,28 +652,18 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
doHolepunch,
)
// loop over the sites and call ConfigurePeer for each one
for _, site := range wgData.Sites {
for i := range wgData.Sites {
site := &wgData.Sites[i] // Use a pointer to modify the struct in the slice
if httpServer != nil {
httpServer.UpdatePeerStatus(site.SiteId, false, 0, site.Endpoint, false)
}
err = ConfigurePeer(dev, site, privateKey, endpoint)
if err != nil {
logger.Error("Failed to configure peer: %v", err)
return
}
err = addRouteForServerIP(site.ServerIP, interfaceName)
if err != nil {
logger.Error("Failed to add route for peer: %v", err)
return
}
// Format the endpoint before configuring the peer.
site.Endpoint = formatEndpoint(site.Endpoint)
// Add routes for remote subnets
if err := addRoutesForRemoteSubnets(site.RemoteSubnets, interfaceName); err != nil {
logger.Error("Failed to add routes for remote subnets: %v", err)
return
}
if err := ConfigurePeer(dev, *site, privateKey, endpoint); err != nil { logger.Error("Failed to configure peer: %v", err); return }
if err := addRouteForServerIP(site.ServerIP, interfaceName); err != nil { logger.Error("Failed to add route for peer: %v", err); return }
if err := addRoutesForRemoteSubnets(site.RemoteSubnets, interfaceName); err != nil { logger.Error("Failed to add routes for remote subnets: %v", err); return }
logger.Info("Configured peer %s", site.PublicKey)
}
@@ -748,12 +710,11 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
break
}
}
if err := ConfigurePeer(dev, siteConfig, privateKey, endpoint); err != nil {
logger.Error("Failed to update peer: %v", err)
// Send error response if needed
return
}
// Format the endpoint before updating the peer.
siteConfig.Endpoint = formatEndpoint(siteConfig.Endpoint)
if err := ConfigurePeer(dev, siteConfig, privateKey, endpoint); err != nil { logger.Error("Failed to update peer: %v", err); return }
// Remove old remote subnet routes if they changed
if oldRemoteSubnets != siteConfig.RemoteSubnets {
@@ -771,12 +732,8 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
// Update successful
logger.Info("Successfully updated peer for site %d", updateData.SiteId)
// If this is part of a WgData structure, update it
for i, site := range wgData.Sites {
if site.SiteId == updateData.SiteId {
wgData.Sites[i] = siteConfig
break
}
for i := range wgData.Sites {
if wgData.Sites[i].SiteId == updateData.SiteId { wgData.Sites[i] = siteConfig; break }
}
} else {
logger.Error("WireGuard device not initialized")
@@ -811,23 +768,12 @@ func runOlmMainWithArgs(ctx context.Context, args []string) {
// Add the peer to WireGuard
if dev != nil {
if err := ConfigurePeer(dev, siteConfig, privateKey, endpoint); err != nil {
logger.Error("Failed to add peer: %v", err)
return
}
// Format the endpoint before adding the new peer.
siteConfig.Endpoint = formatEndpoint(siteConfig.Endpoint)
// Add route for the new peer
err = addRouteForServerIP(siteConfig.ServerIP, interfaceName)
if err != nil {
logger.Error("Failed to add route for new peer: %v", err)
return
}
// Add routes for remote subnets
if err := addRoutesForRemoteSubnets(siteConfig.RemoteSubnets, interfaceName); err != nil {
logger.Error("Failed to add routes for remote subnets: %v", err)
return
}
if err := ConfigurePeer(dev, siteConfig, privateKey, endpoint); err != nil { logger.Error("Failed to add peer: %v", err); return }
if err := addRouteForServerIP(siteConfig.ServerIP, interfaceName); err != nil { logger.Error("Failed to add route for new peer: %v", err); return }
if err := addRoutesForRemoteSubnets(siteConfig.RemoteSubnets, interfaceName); err != nil { logger.Error("Failed to add routes for remote subnets: %v", err); return }
// Add successful
logger.Info("Successfully added peer for site %d", addData.SiteId)

View File

@@ -3,6 +3,7 @@ package peermonitor
import (
"context"
"fmt"
"strings"
"sync"
"time"
@@ -204,12 +205,18 @@ func (pm *PeerMonitor) HandleFailover(siteID int, relayEndpoint string) {
return
}
// Check for IPv6 and format the endpoint correctly
formattedEndpoint := relayEndpoint
if strings.Contains(relayEndpoint, ":") {
formattedEndpoint = fmt.Sprintf("[%s]", relayEndpoint)
}
// Configure WireGuard to use the relay
wgConfig := fmt.Sprintf(`private_key=%s
public_key=%s
allowed_ip=%s/32
endpoint=%s:21820
persistent_keepalive_interval=1`, pm.privateKey, config.PublicKey, config.ServerIP, relayEndpoint)
persistent_keepalive_interval=1`, pm.privateKey, config.PublicKey, config.ServerIP, formattedEndpoint)
err := pm.device.IpcSet(wgConfig)
if err != nil {