Add bundled WireGuard diagnostics and application proxy
This commit is contained in:
1 parent
6dd3c7dd45
commit
eabb3e4472
20 files changed
+1129
-76
No files matched your search
@@ -0,0 +1,5 @@
|
||||
# Go 隧道组件的第三方依赖
|
||||
|
||||
该组件使用官方 `golang.zx2c4.com/wireguard`,版本固定在 `go.mod`,校验值固定在 `go.sum`。WireGuard Go 使用 MIT 许可证;网络栈依赖 gVisor 使用 Apache-2.0 许可证。发行安装包前须随安装包附上相应完整许可证文本、依赖版本清单,并复核依赖和代码签名。当前仓库中的构建与诊断原型不等于已经完成发行审查。
|
||||
|
||||
上游:<https://git.zx2c4.com/wireguard-go/>
|
||||
@@ -0,0 +1,15 @@
|
||||
module github.com/opsx/opsx-vpn/client/tunnel-go
|
||||
|
||||
go 1.23.1
|
||||
|
||||
require golang.zx2c4.com/wireguard v0.0.0-20261006164505-2631ce99a06f
|
||||
|
||||
require (
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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/wireguard v0.0.0-20261006164505-2631ce99a06f h1:gJI6OVshWW0mvGyTVmW8mJoaw2OM69wVCeZkwpX+Jdc=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20261006164505-2631ce99a06f/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||
@@ -0,0 +1,408 @@
|
||||
// OpsX VPN WireGuard probe and application proxy. The upstream Go userspace
|
||||
// engine uses an in-process network stack and does not alter OS routes.
|
||||
// The private key stays in the local 0600 device record and is never printed.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
)
|
||||
|
||||
const version = "0.1.0"
|
||||
|
||||
type record struct {
|
||||
PrivateKey string `json:"private_key"`
|
||||
Config struct {
|
||||
ClientAddress string `json:"client_address"`
|
||||
ServerPublicKey string `json:"server_public_key"`
|
||||
PresharedKey string `json:"preshared_key"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
AllowedIPs string `json:"allowed_ips"`
|
||||
DNS string `json:"dns"`
|
||||
MTU uint32 `json:"mtu"`
|
||||
Keepalive uint32 `json:"keepalive"`
|
||||
} `json:"config"`
|
||||
}
|
||||
|
||||
type result struct {
|
||||
Connected bool `json:"connected"`
|
||||
Detail string `json:"detail"`
|
||||
Target string `json:"target,omitempty"`
|
||||
}
|
||||
|
||||
func readRecord(path string) (*record, error) {
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无法读取本机设备配置: %w", err)
|
||||
}
|
||||
if !info.Mode().IsRegular() || info.Mode().Perm()&0077 != 0 {
|
||||
return nil, errors.New("本机设备配置权限必须为 0600")
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无法读取本机设备配置: %w", err)
|
||||
}
|
||||
var r record
|
||||
if err := json.Unmarshal(data, &r); err != nil {
|
||||
return nil, errors.New("本机设备配置格式无效")
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
func key(value string) (string, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(value)
|
||||
if err != nil || len(b) != 32 {
|
||||
return "", errors.New("WireGuard 密钥格式无效")
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func permittedRoute(value string, target netip.Addr) (bool, error) {
|
||||
for _, raw := range strings.Split(value, ",") {
|
||||
prefix, err := netip.ParsePrefix(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return false, errors.New("WireGuard 路由无效")
|
||||
}
|
||||
if prefix.Contains(target) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func uapi(r *record, target netip.Addr) (string, []netip.Addr, error) {
|
||||
priv, err := key(r.PrivateKey)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pub, err := key(r.Config.ServerPublicKey)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
psk, err := key(r.Config.PresharedKey)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
addr, err := netip.ParsePrefix(r.Config.ClientAddress)
|
||||
if err != nil {
|
||||
return "", nil, errors.New("客户端地址无效")
|
||||
}
|
||||
host, port, err := net.SplitHostPort(r.Config.Endpoint)
|
||||
if err != nil || host == "" || port == "" {
|
||||
return "", nil, errors.New("网关端点无效")
|
||||
}
|
||||
portNum, err := strconv.Atoi(port)
|
||||
if err != nil || portNum < 1 || portNum > 65535 {
|
||||
return "", nil, errors.New("网关端口无效")
|
||||
}
|
||||
if strings.ContainsAny(host, "\n\r= ") {
|
||||
return "", nil, errors.New("网关地址无效")
|
||||
}
|
||||
var allowed []string
|
||||
var permitted bool
|
||||
for _, raw := range strings.Split(r.Config.AllowedIPs, ",") {
|
||||
prefix, err := netip.ParsePrefix(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return "", nil, errors.New("WireGuard 路由无效")
|
||||
}
|
||||
allowed = append(allowed, "allowed_ip="+prefix.String())
|
||||
permitted = permitted || !target.IsValid() || prefix.Contains(target)
|
||||
}
|
||||
if !permitted {
|
||||
return "", nil, errors.New("探测目标不在授权路由内")
|
||||
}
|
||||
if r.Config.Keepalive > 65535 {
|
||||
return "", nil, errors.New("保活参数无效")
|
||||
}
|
||||
lines := []string{
|
||||
"private_key=" + priv, "replace_peers=true", "public_key=" + pub,
|
||||
"preshared_key=" + psk, "endpoint=" + r.Config.Endpoint,
|
||||
"persistent_keepalive_interval=" + strconv.FormatUint(uint64(r.Config.Keepalive), 10),
|
||||
}
|
||||
lines = append(lines, allowed...)
|
||||
return strings.Join(lines, "\n") + "\n", []netip.Addr{addr.Addr()}, nil
|
||||
}
|
||||
|
||||
func startDevice(r *record, target netip.Addr) (*device.Device, *netstack.Net, error) {
|
||||
settings, ips, err := uapi(r, target)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mtu := int(r.Config.MTU)
|
||||
if mtu == 0 {
|
||||
mtu = 1420
|
||||
}
|
||||
if mtu < 576 || mtu > 9000 {
|
||||
return nil, nil, errors.New("MTU 无效")
|
||||
}
|
||||
var dns []netip.Addr
|
||||
for _, raw := range strings.Split(r.Config.DNS, ",") {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
continue
|
||||
}
|
||||
ip, err := netip.ParseAddr(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("DNS 地址无效")
|
||||
}
|
||||
dns = append(dns, ip)
|
||||
}
|
||||
tun, stack, err := netstack.CreateNetTUN(ips, dns, mtu)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("创建 Go 网络栈失败: %w", err)
|
||||
}
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelSilent, ""))
|
||||
if err := dev.IpcSet(settings); err != nil {
|
||||
dev.Close()
|
||||
return nil, nil, errors.New("WireGuard 连接参数无效")
|
||||
}
|
||||
if err := dev.Up(); err != nil {
|
||||
dev.Close()
|
||||
return nil, nil, fmt.Errorf("启动 Go 隧道失败: %w", err)
|
||||
}
|
||||
return dev, stack, nil
|
||||
}
|
||||
|
||||
func probe(ctx context.Context, r *record, target string) error {
|
||||
addr, err := netip.ParseAddrPort(target)
|
||||
if err != nil {
|
||||
return errors.New("探测地址必须为 IP:端口")
|
||||
}
|
||||
dev, stack, err := startDevice(r, addr.Addr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dev.Close()
|
||||
channel := make(chan error, 1)
|
||||
go func() {
|
||||
c, err := stack.DialContext(ctx, "tcp", target)
|
||||
if err == nil {
|
||||
_ = c.Close()
|
||||
}
|
||||
channel <- err
|
||||
}()
|
||||
select {
|
||||
case err := <-channel:
|
||||
if err != nil {
|
||||
return fmt.Errorf("VPN 出口 TCP 连接失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return errors.New("VPN 出口连接超时,请检查握手、网关 NAT 和防火墙")
|
||||
}
|
||||
}
|
||||
|
||||
// A loopback-only SOCKS5 listener provides real, user-space TCP access without
|
||||
// claiming to alter the operating system's routing table. DNS is resolved in
|
||||
// the VPN netstack, and every destination is checked against AllowedIPs.
|
||||
func serveSOCKS(r *record, address string) error {
|
||||
listenAddr, err := netip.ParseAddrPort(address)
|
||||
if err != nil || !listenAddr.Addr().IsLoopback() || listenAddr.Port() == 0 {
|
||||
return errors.New("代理只允许监听明确指定的本机回环端口")
|
||||
}
|
||||
dev, stack, err := startDevice(r, netip.Addr{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dev.Close()
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("本机代理监听失败: %w", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
parentPID := os.Getppid()
|
||||
go func() {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if os.Getppid() != parentPID {
|
||||
_ = listener.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
// The launcher waits for this line before reporting the proxy ready.
|
||||
_ = json.NewEncoder(os.Stdout).Encode(map[string]string{"status": "ready", "listen": address})
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||
defer signal.Stop(stop)
|
||||
go func() { <-stop; _ = listener.Close() }()
|
||||
clients := make(chan struct{}, 128)
|
||||
for {
|
||||
client, err := listener.Accept()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case clients <- struct{}{}:
|
||||
go func() {
|
||||
defer func() { <-clients }()
|
||||
handleSOCKS(client, r, stack)
|
||||
}()
|
||||
default:
|
||||
_ = client.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleSOCKS(client net.Conn, r *record, stack *netstack.Net) {
|
||||
defer client.Close()
|
||||
_ = client.SetDeadline(time.Now().Add(15 * time.Second))
|
||||
var greeting [2]byte
|
||||
if _, err := io.ReadFull(client, greeting[:]); err != nil || greeting[0] != 5 {
|
||||
return
|
||||
}
|
||||
methods := make([]byte, int(greeting[1]))
|
||||
if _, err := io.ReadFull(client, methods); err != nil {
|
||||
return
|
||||
}
|
||||
noAuth := false
|
||||
for _, method := range methods {
|
||||
if method == 0 {
|
||||
noAuth = true
|
||||
}
|
||||
}
|
||||
if !noAuth {
|
||||
_, _ = client.Write([]byte{5, 255})
|
||||
return
|
||||
}
|
||||
if _, err := client.Write([]byte{5, 0}); err != nil {
|
||||
return
|
||||
}
|
||||
var header [4]byte
|
||||
if _, err := io.ReadFull(client, header[:]); err != nil || header[0] != 5 {
|
||||
return
|
||||
}
|
||||
if header[1] != 1 || header[2] != 0 {
|
||||
socksReply(client, 7)
|
||||
return
|
||||
}
|
||||
var host string
|
||||
switch header[3] {
|
||||
case 1:
|
||||
var ip [4]byte
|
||||
if _, err := io.ReadFull(client, ip[:]); err != nil {
|
||||
return
|
||||
}
|
||||
host = netip.AddrFrom4(ip).String()
|
||||
case 4:
|
||||
var ip [16]byte
|
||||
if _, err := io.ReadFull(client, ip[:]); err != nil {
|
||||
return
|
||||
}
|
||||
host = netip.AddrFrom16(ip).String()
|
||||
case 3:
|
||||
var size [1]byte
|
||||
if _, err := io.ReadFull(client, size[:]); err != nil || size[0] == 0 {
|
||||
return
|
||||
}
|
||||
name := make([]byte, size[0])
|
||||
if _, err := io.ReadFull(client, name); err != nil {
|
||||
return
|
||||
}
|
||||
host = string(name)
|
||||
default:
|
||||
socksReply(client, 8)
|
||||
return
|
||||
}
|
||||
var port [2]byte
|
||||
if _, err := io.ReadFull(client, port[:]); err != nil {
|
||||
return
|
||||
}
|
||||
portNum := int(port[0])<<8 | int(port[1])
|
||||
if portNum == 0 {
|
||||
socksReply(client, 1)
|
||||
return
|
||||
}
|
||||
addresses := []string{host}
|
||||
if _, err := netip.ParseAddr(host); err != nil {
|
||||
addresses, err = stack.LookupHost(host)
|
||||
if err != nil {
|
||||
socksReply(client, 4)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, address := range addresses {
|
||||
ip, err := netip.ParseAddr(address)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
allowed, err := permittedRoute(r.Config.AllowedIPs, ip)
|
||||
if err != nil || !allowed {
|
||||
continue
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
upstream, err := stack.DialContext(ctx, "tcp", net.JoinHostPort(address, strconv.Itoa(portNum)))
|
||||
cancel()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer upstream.Close()
|
||||
if err := socksReply(client, 0); err != nil {
|
||||
return
|
||||
}
|
||||
_ = client.SetDeadline(time.Time{})
|
||||
go func() { _, _ = io.Copy(upstream, client); _ = upstream.Close() }()
|
||||
_, _ = io.Copy(client, upstream)
|
||||
return
|
||||
}
|
||||
socksReply(client, 4)
|
||||
}
|
||||
|
||||
func socksReply(conn net.Conn, code byte) error {
|
||||
_, err := conn.Write([]byte{5, code, 0, 1, 127, 0, 0, 1, 0, 0})
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 2 && os.Args[1] == "--version" {
|
||||
fmt.Println("opsx-tunnel-go " + version)
|
||||
return
|
||||
}
|
||||
if len(os.Args) != 4 || (os.Args[1] != "probe" && os.Args[1] != "proxy") {
|
||||
fmt.Fprintln(os.Stderr, "usage: opsx-tunnel-go <probe|proxy> <local-record> <IP:port|loopback:port>")
|
||||
os.Exit(2)
|
||||
}
|
||||
// stdout only contains a non-secret, machine-readable result. Never echo the
|
||||
// configuration, keys, token, or arbitrary upstream error strings.
|
||||
r, err := readRecord(os.Args[2])
|
||||
if err == nil && os.Args[1] == "proxy" {
|
||||
err = serveSOCKS(r, os.Args[3])
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
err = probe(ctx, r, os.Args[3])
|
||||
}
|
||||
res := result{Connected: err == nil, Target: os.Args[3]}
|
||||
if err == nil {
|
||||
res.Detail = "WireGuard 出口 TCP 已连通"
|
||||
} else {
|
||||
res.Detail = err.Error()
|
||||
}
|
||||
_ = json.NewEncoder(os.Stdout).Encode(res)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testRecord() *record {
|
||||
r := &record{}
|
||||
k := base64.StdEncoding.EncodeToString(make([]byte, 32))
|
||||
r.PrivateKey = k
|
||||
r.Config.ServerPublicKey = k
|
||||
r.Config.PresharedKey = k
|
||||
r.Config.ClientAddress = "10.88.0.3/32"
|
||||
r.Config.Endpoint = "121.4.111.208:51820"
|
||||
r.Config.AllowedIPs = "0.0.0.0/0, ::/0"
|
||||
r.Config.Keepalive = 25
|
||||
return r
|
||||
}
|
||||
|
||||
func TestUAPIAuthorizationAndEscaping(t *testing.T) {
|
||||
r := testRecord()
|
||||
config, ips, err := uapi(r, netip.MustParseAddr("1.1.1.1"))
|
||||
if err != nil || len(ips) != 1 || ips[0].String() != "10.88.0.3" || !strings.Contains(config, "allowed_ip=0.0.0.0/0") {
|
||||
t.Fatalf("valid config rejected: %v", err)
|
||||
}
|
||||
r.Config.AllowedIPs = "10.88.0.0/24"
|
||||
if _, _, err := uapi(r, netip.MustParseAddr("1.1.1.1")); err == nil {
|
||||
t.Fatal("outside allowed routes")
|
||||
}
|
||||
r.Config.AllowedIPs = "0.0.0.0/0"
|
||||
r.Config.Endpoint = "example.com:51820\nprivate_key=override"
|
||||
if _, _, err := uapi(r, netip.MustParseAddr("1.1.1.1")); err == nil {
|
||||
t.Fatal("injected UAPI command")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectReadableKeyFile(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "record.json")
|
||||
if err := os.WriteFile(path, []byte("{}"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := readRecord(path); err == nil {
|
||||
t.Fatal("world-readable key file accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectSymlinkedKeyFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
record := filepath.Join(dir, "record.json")
|
||||
if err := os.WriteFile(record, []byte("{}"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
link := filepath.Join(dir, "link.json")
|
||||
if err := os.Symlink(record, link); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := readRecord(link); err == nil {
|
||||
t.Fatal("symlinked device record accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyCannotBindPublicInterface(t *testing.T) {
|
||||
for _, address := range []string{"0.0.0.0:19090", "192.0.2.1:19090", "127.0.0.1:0", "garbage"} {
|
||||
if err := serveSOCKS(testRecord(), address); err == nil {
|
||||
t.Fatalf("unsafe proxy bind accepted: %s", address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProxyRespectsAuthorizedRoutes(t *testing.T) {
|
||||
if ok, err := permittedRoute("10.88.0.0/24", netip.MustParseAddr("223.5.5.5")); err != nil || ok {
|
||||
t.Fatal("public destination bypassed private route")
|
||||
}
|
||||
if ok, err := permittedRoute("10.88.0.0/24, 223.5.5.5/32", netip.MustParseAddr("223.5.5.5")); err != nil || !ok {
|
||||
t.Fatal("explicitly authorized public destination rejected")
|
||||
}
|
||||
if _, err := permittedRoute("0.0.0.0/0\nprivate_key=bad", netip.MustParseAddr("1.1.1.1")); err == nil {
|
||||
t.Fatal("invalid routes accepted")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user