mirror of
https://github.com/whyvl/wireproxy.git
synced 2025-06-27 16:48:01 +02:00
Enhance VPN health checks and connectivity validation
- **README.md**: Added a 'Fork Features' section to highlight new enhancements like Google health checks, IP validation, and program termination for VPN health checks. - **main.go**: Integrated new health check logic. - **http.go**: Added 'responseWith' function and imported 'strconv' for better HTTP response handling. - **wireguard.go**: - Added imports for 'log', 'os', 'strings', and 'time'. - Enhanced 'StartWireguard' with handshake verification and Google connectivity checks. - Implemented program termination on critical check failures. - **utils.go**: - Renamed from 'util.go'. - Added 'CheckGoogleConnectivity' and 'checkIP' functions for external connectivity validation. - **config.go**: No changes. - **net.go**: No changes. - **routine.go**: No changes. These updates improve the reliability, security, and maintainability of the WireGuard VPN management system by ensuring proper connectivity and health checks.
This commit is contained in:
parent
e749217090
commit
1805406a04
9 changed files with 154 additions and 31 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,3 +4,5 @@
|
|||
/.idea
|
||||
.goreleaser.yml
|
||||
*.conf
|
||||
.DS_Store
|
||||
*/.DS_Store
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
|
||||
A wireguard client that exposes itself as a socks5/http proxy or tunnels.
|
||||
|
||||
## Fork Features (Added by Wope Team)
|
||||
This fork introduces several new features to enhance VPN health checks and connectivity validation:
|
||||
- **Google Connectivity Check**: Ensures the VPN connection is capable of accessing external resources by performing a connectivity check to Google.
|
||||
- **IP Check Logic**: Verifies the public IP address using an external service to ensure the VPN is correctly routing traffic.
|
||||
- **Program Termination on Failure**: If critical checks (like handshake and Google connectivity) fail, the program logs detailed error messages and exits, preventing the system from running in an unreliable state.
|
||||
|
||||
# What is this
|
||||
`wireproxy` is a completely userspace application that connects to a wireguard peer,
|
||||
and exposes a socks5/http proxy or tunnels on the machine. This can be useful if you need
|
||||
|
@ -251,4 +257,4 @@ If nothing is set for `CheckAlive`, an empty JSON object with 200 will be the re
|
|||
The peer which the ICMP ping packet is routed to depends on the `AllowedIPs` set for each peers.
|
||||
|
||||
# Stargazers over time
|
||||
[](https://starchart.cc/octeep/wireproxy)
|
||||
[](https://starchart.cc/octeep/wireproxy)
|
BIN
cmd/.DS_Store
vendored
Normal file
BIN
cmd/.DS_Store
vendored
Normal file
Binary file not shown.
|
@ -10,6 +10,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
|
@ -213,6 +214,9 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
// Extract the configuration name from the file path
|
||||
configName := filepath.Base(*config)
|
||||
|
||||
// Wireguard doesn't allow configuring which FD to use for logging
|
||||
// https://github.com/WireGuard/wireguard-go/blob/master/device/logger.go#L39
|
||||
// so redirect STDOUT to STDERR, we don't want to print anything to STDOUT anyways
|
||||
|
@ -224,7 +228,7 @@ func main() {
|
|||
|
||||
lock("ready")
|
||||
|
||||
tun, err := wireproxy.StartWireguard(conf.Device, logLevel)
|
||||
tun, err := wireproxy.StartWireguard(conf.Device, logLevel, configName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -245,4 +249,4 @@ func main() {
|
|||
}
|
||||
|
||||
<-ctx.Done()
|
||||
}
|
||||
}
|
17
http.go
17
http.go
|
@ -9,6 +9,7 @@ import (
|
|||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv" // Add the missing import
|
||||
"strings"
|
||||
|
||||
"github.com/sourcegraph/conc"
|
||||
|
@ -160,3 +161,19 @@ func (s *HTTPServer) ListenAndServe(network, addr string) error {
|
|||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// responseWith constructs an HTTP response with the given status code
|
||||
func responseWith(req *http.Request, statusCode int) *http.Response {
|
||||
statusText := http.StatusText(statusCode)
|
||||
body := "wireproxy:" + " " + req.Proto + " " + strconv.Itoa(statusCode) + " " + statusText + "\r\n"
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: statusCode,
|
||||
Status: statusText,
|
||||
Proto: req.Proto,
|
||||
ProtoMajor: req.ProtoMajor,
|
||||
ProtoMinor: req.ProtoMinor,
|
||||
Header: http.Header{},
|
||||
Body: io.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
}
|
||||
|
|
25
util.go
25
util.go
|
@ -1,25 +0,0 @@
|
|||
package wireproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const space = " "
|
||||
|
||||
func responseWith(req *http.Request, statusCode int) *http.Response {
|
||||
statusText := http.StatusText(statusCode)
|
||||
body := "wireproxy:" + space + req.Proto + space + strconv.Itoa(statusCode) + space + statusText + "\r\n"
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: statusCode,
|
||||
Status: statusText,
|
||||
Proto: req.Proto,
|
||||
ProtoMajor: req.ProtoMajor,
|
||||
ProtoMinor: req.ProtoMinor,
|
||||
Header: http.Header{},
|
||||
Body: io.NopCloser(bytes.NewBufferString(body)),
|
||||
}
|
||||
}
|
87
utils.go
Normal file
87
utils.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package wireproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
)
|
||||
|
||||
// CheckGoogleConnectivity checks Google connectivity using the WireGuard tunnel's network stack
|
||||
func CheckGoogleConnectivity(tun *netstack.Net, configName string) error {
|
||||
// Step 1: Check IP using icanhazip.com
|
||||
ip, err := checkIP(tun, configName)
|
||||
if err != nil {
|
||||
log.Printf("All retries are completed, VPN is not working. Config name: %s", configName)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Printf("IP connectivity check successful: %s", ip)
|
||||
|
||||
// Step 2: Check Google connectivity with redirect handling
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: tun.DialContext,
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
// Prevent automatic redirect following
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest("GET", "https://www.google.com/search?q=what+is+my+ip&num=100", nil)
|
||||
if err != nil {
|
||||
log.Printf("All retries are completed, VPN is not working. Config name: %s", configName)
|
||||
os.Exit(1)
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("All retries are completed, VPN is not working. Config name: %s", configName)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Log the status code directly from the first response
|
||||
log.Printf("Google connectivity check returned status %d", resp.StatusCode)
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
log.Println("Google connectivity check successful: Status 200")
|
||||
os.Exit(0)
|
||||
} else if resp.StatusCode == 302 {
|
||||
log.Printf("Google connectivity check returned status %d (redirect), terminating process. Config name: %s\n", resp.StatusCode, configName)
|
||||
os.Exit(1)
|
||||
} else if resp.StatusCode >= 100 && resp.StatusCode <= 599 {
|
||||
log.Printf("Google connectivity check returned status %d, terminating process. Config name: %s\n", resp.StatusCode, configName)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
log.Printf("Unexpected status code: %d, terminating process. Config name: %s\n", resp.StatusCode, configName)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkIP checks the public IP using the WireGuard tunnel's network stack
|
||||
func checkIP(tun *netstack.Net, configName string) (string, error) {
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: tun.DialContext,
|
||||
},
|
||||
}
|
||||
resp, err := client.Get("https://icanhazip.com")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Get \"https://icanhazip.com\": %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ReadAll: %v", err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(body)), nil // Trim any extra whitespace
|
||||
}
|
38
wireguard.go
38
wireguard.go
|
@ -3,8 +3,11 @@ package wireproxy
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"log"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc/v2"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
|
@ -59,7 +62,7 @@ func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
|
|||
}
|
||||
|
||||
// StartWireguard creates a tun interface on netstack given a configuration
|
||||
func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
|
||||
func StartWireguard(conf *DeviceConfig, logLevel int, configName string) (*VirtualTun, error) {
|
||||
setting, err := createIPCRequest(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -80,6 +83,35 @@ func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure handshake is established
|
||||
for _, peer := range conf.Peers {
|
||||
if peer.Endpoint != nil {
|
||||
// Check handshake status
|
||||
handshakeEstablished := false
|
||||
for i := 0; i < 3; i++ { // Retry for a few seconds
|
||||
peerStatus, err := dev.IpcGet()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get device status: %w", err)
|
||||
}
|
||||
if strings.Contains(peerStatus, *peer.Endpoint) {
|
||||
handshakeEstablished = true
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if !handshakeEstablished {
|
||||
log.Printf("All retries are completed, VPN is not working. Config name: %s", configName)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform Google connectivity check only if the handshake is successful
|
||||
err = CheckGoogleConnectivity(tnet, configName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &VirtualTun{
|
||||
Tnet: tnet,
|
||||
Dev: dev,
|
||||
|
@ -87,4 +119,4 @@ func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
|
|||
SystemDNS: len(setting.dns) == 0,
|
||||
PingRecord: make(map[string]uint64),
|
||||
}, nil
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue