mirror of
https://github.com/fosrl/newt.git
synced 2026-03-14 10:55:07 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80ae03997a | ||
|
|
5c94789d9a | ||
|
|
6c65cc8e5e | ||
|
|
a21a8e90fa | ||
|
|
3d5335f2cb | ||
|
|
94788edce3 | ||
|
|
2bbe037544 | ||
|
|
9b015e9f7c | ||
|
|
3305f711b9 | ||
|
|
ff7fe1275b | ||
|
|
1cbf41e094 | ||
|
|
9bc35433ef |
48
.github/workflows/cicd.yml
vendored
48
.github/workflows/cicd.yml
vendored
@@ -587,28 +587,28 @@ jobs:
|
||||
# sarif_file: trivy-ghcr.sarif
|
||||
# category: Image Vulnerability Scan
|
||||
|
||||
# - name: Build binaries
|
||||
# env:
|
||||
# CGO_ENABLED: "0"
|
||||
# GOFLAGS: "-trimpath"
|
||||
# run: |
|
||||
# set -euo pipefail
|
||||
# TAG_VAR="${TAG}"
|
||||
# make go-build-release tag=$TAG_VAR
|
||||
# shell: bash
|
||||
- name: Build binaries
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
GOFLAGS: "-trimpath"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG_VAR="${TAG}"
|
||||
make -j 10 go-build-release tag=$TAG_VAR
|
||||
shell: bash
|
||||
|
||||
# - name: Create GitHub Release
|
||||
# uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
# with:
|
||||
# tag_name: ${{ env.TAG }}
|
||||
# generate_release_notes: true
|
||||
# prerelease: ${{ env.IS_RC == 'true' }}
|
||||
# files: |
|
||||
# bin/*
|
||||
# fail_on_unmatched_files: true
|
||||
# draft: true
|
||||
# body: |
|
||||
# ## Container Images
|
||||
# - GHCR: `${{ env.GHCR_REF }}`
|
||||
# - Docker Hub: `${{ env.DH_REF || 'N/A' }}`
|
||||
# **Digest:** `${{ steps.build.outputs.digest }}`
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
tag_name: ${{ env.TAG }}
|
||||
generate_release_notes: true
|
||||
prerelease: ${{ env.IS_RC == 'true' }}
|
||||
files: |
|
||||
bin/*
|
||||
fail_on_unmatched_files: true
|
||||
draft: true
|
||||
body: |
|
||||
## Container Images
|
||||
- GHCR: `${{ env.GHCR_REF }}`
|
||||
- Docker Hub: `${{ env.DH_REF || 'N/A' }}`
|
||||
**Digest:** `${{ steps.build.outputs.digest }}`
|
||||
|
||||
12
Makefile
12
Makefile
@@ -27,6 +27,18 @@ docker-build-release:
|
||||
go-build-release-darwin-amd64 go-build-release-windows-amd64 \
|
||||
go-build-release-freebsd-amd64 go-build-release-freebsd-arm64
|
||||
|
||||
go-build-release: \
|
||||
go-build-release-linux-arm64 \
|
||||
go-build-release-linux-arm32-v7 \
|
||||
go-build-release-linux-arm32-v6 \
|
||||
go-build-release-linux-amd64 \
|
||||
go-build-release-linux-riscv64 \
|
||||
go-build-release-darwin-arm64 \
|
||||
go-build-release-darwin-amd64 \
|
||||
go-build-release-windows-amd64 \
|
||||
go-build-release-freebsd-amd64 \
|
||||
go-build-release-freebsd-arm64
|
||||
|
||||
go-build-release-linux-arm64:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/newt_linux_arm64
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func setupClients(client *websocket.Client) {
|
||||
|
||||
host = strings.TrimSuffix(host, "/")
|
||||
|
||||
logger.Info("Setting up clients with netstack2...")
|
||||
logger.Debug("Setting up clients with netstack2...")
|
||||
|
||||
// if useNativeInterface is true make sure we have permission to use native interface
|
||||
if useNativeInterface {
|
||||
@@ -63,7 +63,7 @@ func closeClients() {
|
||||
}
|
||||
}
|
||||
|
||||
func clientsHandleNewtConnection(publicKey string, endpoint string) {
|
||||
func clientsHandleNewtConnection(publicKey string, endpoint string, relayPort uint16) {
|
||||
if !ready {
|
||||
return
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func clientsHandleNewtConnection(publicKey string, endpoint string) {
|
||||
endpoint = strings.Join(parts[:len(parts)-1], ":")
|
||||
|
||||
if wgService != nil {
|
||||
wgService.StartHolepunch(publicKey, endpoint)
|
||||
wgService.StartHolepunch(publicKey, endpoint, relayPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ func NewWireGuardService(interfaceName string, port uint16, mtu int, host string
|
||||
// Add a reference for the hole punch manager (creator already has one reference for WireGuard)
|
||||
sharedBind.AddRef()
|
||||
|
||||
logger.Info("Created shared UDP socket on port %d (refcount: %d)", port, sharedBind.GetRefCount())
|
||||
logger.Debug("Created shared UDP socket on port %d (refcount: %d)", port, sharedBind.GetRefCount())
|
||||
|
||||
// Parse DNS addresses
|
||||
dnsAddrs := []netip.Addr{netip.MustParseAddr(dns)}
|
||||
@@ -270,16 +270,21 @@ func (s *WireGuardService) SetOnNetstackClose(callback func()) {
|
||||
}
|
||||
|
||||
// StartHolepunch starts hole punching to a specific endpoint
|
||||
func (s *WireGuardService) StartHolepunch(publicKey string, endpoint string) {
|
||||
func (s *WireGuardService) StartHolepunch(publicKey string, endpoint string, relayPort uint16) {
|
||||
if s.holePunchManager == nil {
|
||||
logger.Warn("Hole punch manager not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
if relayPort == 0 {
|
||||
relayPort = 21820
|
||||
}
|
||||
|
||||
// Convert websocket.ExitNode to holepunch.ExitNode
|
||||
hpExitNodes := []holepunch.ExitNode{
|
||||
{
|
||||
Endpoint: endpoint,
|
||||
RelayPort: relayPort,
|
||||
PublicKey: publicKey,
|
||||
},
|
||||
}
|
||||
@@ -289,7 +294,7 @@ func (s *WireGuardService) StartHolepunch(publicKey string, endpoint string) {
|
||||
logger.Warn("Failed to start hole punch: %v", err)
|
||||
}
|
||||
|
||||
logger.Info("Starting hole punch to %s with public key: %s", endpoint, publicKey)
|
||||
logger.Debug("Starting hole punch to %s with public key: %s", endpoint, publicKey)
|
||||
}
|
||||
|
||||
// StartDirectUDPRelay starts a direct UDP relay from the main tunnel netstack to the clients' WireGuard.
|
||||
@@ -336,7 +341,7 @@ func (s *WireGuardService) StartDirectUDPRelay(tunnelIP string) error {
|
||||
// Set the netstack connection on the SharedBind so responses go back through the tunnel
|
||||
s.sharedBind.SetNetstackConn(listener)
|
||||
|
||||
logger.Info("Started direct UDP relay on %s:%d (bidirectional via SharedBind)", tunnelIP, s.Port)
|
||||
logger.Debug("Started direct UDP relay on %s:%d (bidirectional via SharedBind)", tunnelIP, s.Port)
|
||||
|
||||
// Start the relay goroutine to read from netstack and inject into SharedBind
|
||||
s.directRelayWg.Add(1)
|
||||
@@ -354,7 +359,7 @@ func (s *WireGuardService) runDirectUDPRelay(listener net.PacketConn) {
|
||||
// Note: Don't close listener here - it's also used by SharedBind for sending responses
|
||||
// It will be closed when the relay is stopped
|
||||
|
||||
logger.Info("Direct UDP relay started (bidirectional through SharedBind)")
|
||||
logger.Debug("Direct UDP relay started (bidirectional through SharedBind)")
|
||||
|
||||
buf := make([]byte, 65535) // Max UDP packet size
|
||||
|
||||
@@ -440,7 +445,7 @@ func (s *WireGuardService) LoadRemoteConfig() error {
|
||||
"port": s.Port,
|
||||
}, 2*time.Second)
|
||||
|
||||
logger.Info("Requesting WireGuard configuration from remote server")
|
||||
logger.Debug("Requesting WireGuard configuration from remote server")
|
||||
go s.periodicBandwidthCheck()
|
||||
|
||||
return nil
|
||||
@@ -450,7 +455,7 @@ func (s *WireGuardService) handleConfig(msg websocket.WSMessage) {
|
||||
var config WgConfig
|
||||
|
||||
logger.Debug("Received message: %v", msg)
|
||||
logger.Info("Received WireGuard clients configuration from remote server")
|
||||
logger.Debug("Received WireGuard clients configuration from remote server")
|
||||
|
||||
jsonData, err := json.Marshal(msg.Data)
|
||||
if err != nil {
|
||||
@@ -472,6 +477,8 @@ func (s *WireGuardService) handleConfig(msg websocket.WSMessage) {
|
||||
// Ensure the WireGuard interface and peers are configured
|
||||
if err := s.ensureWireguardInterface(config); err != nil {
|
||||
logger.Error("Failed to ensure WireGuard interface: %v", err)
|
||||
logger.Error("Clients functionality will be disabled until the interface can be created")
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.ensureWireguardPeers(config.Peers); err != nil {
|
||||
@@ -481,6 +488,8 @@ func (s *WireGuardService) handleConfig(msg websocket.WSMessage) {
|
||||
if err := s.ensureTargets(config.Targets); err != nil {
|
||||
logger.Error("Failed to ensure WireGuard targets: %v", err)
|
||||
}
|
||||
|
||||
logger.Info("Client connectivity setup. Ready to accept connections from clients!")
|
||||
}
|
||||
|
||||
func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error {
|
||||
@@ -628,7 +637,7 @@ func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error {
|
||||
return fmt.Errorf("failed to bring up WireGuard device: %v", err)
|
||||
}
|
||||
|
||||
logger.Info("WireGuard netstack device created and configured")
|
||||
logger.Debug("WireGuard netstack device created and configured")
|
||||
|
||||
// Release the mutex before calling the callback
|
||||
s.mu.Unlock()
|
||||
@@ -647,6 +656,11 @@ func (s *WireGuardService) ensureWireguardPeers(peers []Peer) error {
|
||||
// For netstack, we need to manage peers differently
|
||||
// We'll configure peers directly on the device using IPC
|
||||
|
||||
// Check if device is initialized
|
||||
if s.device == nil {
|
||||
return fmt.Errorf("WireGuard device is not initialized")
|
||||
}
|
||||
|
||||
// First, clear all existing peers by getting current config and removing them
|
||||
currentConfig, err := s.device.IpcGet()
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -19,7 +20,9 @@ import (
|
||||
// ExitNode represents a WireGuard exit node for hole punching
|
||||
type ExitNode struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
RelayPort uint16 `json:"relayPort"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
SiteIds []int `json:"siteIds,omitempty"`
|
||||
}
|
||||
|
||||
// Manager handles UDP hole punching operations
|
||||
@@ -140,6 +143,51 @@ func (m *Manager) RemoveExitNode(endpoint string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
RemoveExitNodesByPeer removes the peer ID from the SiteIds list in each exit node.
|
||||
If the SiteIds list becomes empty after removal, the exit node is removed entirely.
|
||||
Returns the number of exit nodes removed.
|
||||
*/
|
||||
func (m *Manager) RemoveExitNodesByPeer(peerID int) int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
removed := 0
|
||||
for endpoint, node := range m.exitNodes {
|
||||
// Remove peerID from SiteIds if present
|
||||
newSiteIds := make([]int, 0, len(node.SiteIds))
|
||||
for _, id := range node.SiteIds {
|
||||
if id != peerID {
|
||||
newSiteIds = append(newSiteIds, id)
|
||||
}
|
||||
}
|
||||
if len(newSiteIds) != len(node.SiteIds) {
|
||||
node.SiteIds = newSiteIds
|
||||
if len(node.SiteIds) == 0 {
|
||||
delete(m.exitNodes, endpoint)
|
||||
logger.Info("Removed exit node %s as no more site IDs remain after removing peer %d", endpoint, peerID)
|
||||
removed++
|
||||
} else {
|
||||
m.exitNodes[endpoint] = node
|
||||
logger.Info("Removed peer %d from exit node %s site IDs", peerID, endpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if removed > 0 {
|
||||
// Signal the goroutine to refresh if running
|
||||
if m.running && m.updateChan != nil {
|
||||
select {
|
||||
case m.updateChan <- struct{}{}:
|
||||
default:
|
||||
// Channel full or closed, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
// GetExitNodes returns a copy of the current exit nodes
|
||||
func (m *Manager) GetExitNodes() []ExitNode {
|
||||
m.mu.Lock()
|
||||
@@ -202,7 +250,7 @@ func (m *Manager) TriggerHolePunch() error {
|
||||
continue
|
||||
}
|
||||
|
||||
serverAddr := net.JoinHostPort(host, "21820")
|
||||
serverAddr := net.JoinHostPort(host, strconv.Itoa(int(exitNode.RelayPort)))
|
||||
remoteAddr, err := net.ResolveUDPAddr("udp", serverAddr)
|
||||
if err != nil {
|
||||
logger.Error("Failed to resolve UDP address %s: %v", serverAddr, err)
|
||||
@@ -247,7 +295,7 @@ func (m *Manager) StartMultipleExitNodes(exitNodes []ExitNode) error {
|
||||
m.updateChan = make(chan struct{}, 1)
|
||||
m.mu.Unlock()
|
||||
|
||||
logger.Info("Starting UDP hole punch to %d exit nodes with shared bind", len(exitNodes))
|
||||
logger.Debug("Starting UDP hole punch to %d exit nodes with shared bind", len(exitNodes))
|
||||
|
||||
go m.runMultipleExitNodes()
|
||||
|
||||
@@ -313,7 +361,7 @@ func (m *Manager) runMultipleExitNodes() {
|
||||
continue
|
||||
}
|
||||
|
||||
serverAddr := net.JoinHostPort(host, "21820")
|
||||
serverAddr := net.JoinHostPort(host, strconv.Itoa(int(exitNode.RelayPort)))
|
||||
remoteAddr, err := net.ResolveUDPAddr("udp", serverAddr)
|
||||
if err != nil {
|
||||
logger.Error("Failed to resolve UDP address %s: %v", serverAddr, err)
|
||||
@@ -325,7 +373,7 @@ func (m *Manager) runMultipleExitNodes() {
|
||||
publicKey: exitNode.PublicKey,
|
||||
endpointName: exitNode.Endpoint,
|
||||
})
|
||||
logger.Info("Resolved exit node: %s -> %s", exitNode.Endpoint, remoteAddr.String())
|
||||
logger.Debug("Resolved exit node: %s -> %s", exitNode.Endpoint, remoteAddr.String())
|
||||
}
|
||||
return resolvedNodes
|
||||
}
|
||||
|
||||
10
main.go
10
main.go
@@ -37,6 +37,7 @@ import (
|
||||
|
||||
type WgData struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
RelayPort uint16 `json:"relayPort"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
ServerIP string `json:"serverIP"`
|
||||
TunnelIP string `json:"tunnelIP"`
|
||||
@@ -419,7 +420,7 @@ func runNewtMain(ctx context.Context) {
|
||||
}
|
||||
if tel != nil {
|
||||
// Admin HTTP server (exposes /metrics when Prometheus exporter is enabled)
|
||||
logger.Info("Starting metrics server on %s", tcfg.AdminAddr)
|
||||
logger.Debug("Starting metrics server on %s", tcfg.AdminAddr)
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) })
|
||||
if tel.PrometheusHandler != nil {
|
||||
@@ -691,7 +692,12 @@ func runNewtMain(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
clientsHandleNewtConnection(wgData.PublicKey, endpoint)
|
||||
relayPort := wgData.RelayPort
|
||||
if relayPort == 0 {
|
||||
relayPort = 21820
|
||||
}
|
||||
|
||||
clientsHandleNewtConnection(wgData.PublicKey, endpoint, relayPort)
|
||||
|
||||
// Configure WireGuard
|
||||
config := fmt.Sprintf(`private_key=%s
|
||||
|
||||
@@ -372,7 +372,7 @@ func copyPacketData(dst, src net.PacketConn, to net.Addr, timeout time.Duration)
|
||||
// InstallICMPHandler installs the ICMP handler on the stack
|
||||
func (h *ICMPHandler) InstallICMPHandler() error {
|
||||
h.stack.SetTransportProtocolHandler(header.ICMPv4ProtocolNumber, h.handleICMPPacket)
|
||||
logger.Info("ICMP Handler: Installed ICMP protocol handler")
|
||||
logger.Debug("ICMP Handler: Installed ICMP protocol handler")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -600,7 +600,7 @@ func (h *ICMPHandler) sendAndReceiveICMP(conn *icmp.PacketConn, actualDstIP stri
|
||||
logger.Debug("ICMP Handler: Reply seq mismatch: got seq=%d, want seq=%d", reply.Seq, seq)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if !ignoreIdent && reply.ID != int(ident) {
|
||||
logger.Debug("ICMP Handler: Reply ident mismatch: got ident=%d, want ident=%d", reply.ID, ident)
|
||||
continue
|
||||
|
||||
@@ -254,7 +254,7 @@ func NewProxyHandler(options ProxyHandlerOptions) (*ProxyHandler, error) {
|
||||
if err := handler.icmpHandler.InstallICMPHandler(); err != nil {
|
||||
return nil, fmt.Errorf("failed to install ICMP handler: %v", err)
|
||||
}
|
||||
logger.Info("ProxyHandler: ICMP handler enabled")
|
||||
logger.Debug("ProxyHandler: ICMP handler enabled")
|
||||
}
|
||||
|
||||
// // Example 1: Add a rule with no port restrictions (all ports allowed)
|
||||
|
||||
@@ -38,7 +38,6 @@ type Server struct {
|
||||
isRunning bool
|
||||
runningLock sync.Mutex
|
||||
newtID string
|
||||
outputPrefix string
|
||||
useNetstack bool
|
||||
tnet interface{} // Will be *netstack2.Net when using netstack
|
||||
}
|
||||
@@ -50,7 +49,6 @@ func NewServer(serverAddr string, serverPort uint16, newtID string) *Server {
|
||||
serverPort: serverPort + 1, // use the next port for the server
|
||||
shutdownCh: make(chan struct{}),
|
||||
newtID: newtID,
|
||||
outputPrefix: "[WGTester] ",
|
||||
useNetstack: false,
|
||||
tnet: nil,
|
||||
}
|
||||
@@ -63,7 +61,6 @@ func NewServerWithNetstack(serverAddr string, serverPort uint16, newtID string,
|
||||
serverPort: serverPort + 1, // use the next port for the server
|
||||
shutdownCh: make(chan struct{}),
|
||||
newtID: newtID,
|
||||
outputPrefix: "[WGTester] ",
|
||||
useNetstack: true,
|
||||
tnet: tnet,
|
||||
}
|
||||
@@ -109,7 +106,7 @@ func (s *Server) Start() error {
|
||||
s.isRunning = true
|
||||
go s.handleConnections()
|
||||
|
||||
logger.Info("%sServer started on %s:%d", s.outputPrefix, s.serverAddr, s.serverPort)
|
||||
logger.Debug("WGTester Server started on %s:%d", s.serverAddr, s.serverPort)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -127,7 +124,7 @@ func (s *Server) Stop() {
|
||||
s.conn.Close()
|
||||
}
|
||||
s.isRunning = false
|
||||
logger.Info("%sServer stopped", s.outputPrefix)
|
||||
logger.Info("WGTester Server stopped")
|
||||
}
|
||||
|
||||
// RestartWithNetstack stops the current server and restarts it with netstack
|
||||
@@ -162,7 +159,7 @@ func (s *Server) handleConnections() {
|
||||
// Set read deadline to avoid blocking forever
|
||||
err := s.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
if err != nil {
|
||||
logger.Error("%sError setting read deadline: %v", s.outputPrefix, err)
|
||||
logger.Error("Error setting read deadline: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -192,7 +189,7 @@ func (s *Server) handleConnections() {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
logger.Error("%sError reading from UDP: %v", s.outputPrefix, err)
|
||||
logger.Error("Error reading from UDP: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -224,7 +221,7 @@ func (s *Server) handleConnections() {
|
||||
copy(responsePacket[5:13], buffer[5:13])
|
||||
|
||||
// Log response being sent for debugging
|
||||
// logger.Debug("%sSending response to %s", s.outputPrefix, addr.String())
|
||||
// logger.Debug("Sending response to %s", addr.String())
|
||||
|
||||
// Send the response packet - handle both regular UDP and netstack UDP
|
||||
if s.useNetstack {
|
||||
@@ -238,9 +235,9 @@ func (s *Server) handleConnections() {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("%sError sending response: %v", s.outputPrefix, err)
|
||||
logger.Error("Error sending response: %v", err)
|
||||
} else {
|
||||
// logger.Debug("%sResponse sent successfully", s.outputPrefix)
|
||||
// logger.Debug("Response sent successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user