You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
7.4 KiB
286 lines
7.4 KiB
2 weeks ago
|
//go:build !windows
|
||
|
// +build !windows
|
||
|
|
||
|
package transparent
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"os/exec"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"syscall"
|
||
|
|
||
|
"github.com/sirupsen/logrus"
|
||
|
)
|
||
|
|
||
|
type TransparentProxy struct {
|
||
|
logger *logrus.Logger
|
||
|
proxyPort int
|
||
|
transparentPort int
|
||
|
dnsPort int
|
||
|
originalDNS []string
|
||
|
rules []string
|
||
|
}
|
||
|
|
||
|
type Config struct {
|
||
|
ProxyPort int // HTTP代理端口
|
||
|
TransparentPort int // 透明代理端口
|
||
|
DNSPort int // DNS代理端口
|
||
|
BypassIPs []string // 不代理的IP列表
|
||
|
BypassDomains []string // 不代理的域名列表
|
||
|
}
|
||
|
|
||
|
func NewTransparentProxy(config Config, logger *logrus.Logger) *TransparentProxy {
|
||
|
if config.TransparentPort == 0 {
|
||
|
config.TransparentPort = 8888
|
||
|
}
|
||
|
if config.DNSPort == 0 {
|
||
|
config.DNSPort = 5353
|
||
|
}
|
||
|
|
||
|
return &TransparentProxy{
|
||
|
logger: logger,
|
||
|
proxyPort: config.ProxyPort,
|
||
|
transparentPort: config.TransparentPort,
|
||
|
dnsPort: config.DNSPort,
|
||
|
rules: make([]string, 0),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetupTransparentProxy 设置透明代理规则
|
||
|
func (tp *TransparentProxy) SetupTransparentProxy(ctx context.Context) error {
|
||
|
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
|
||
|
return fmt.Errorf("transparent proxy only supported on Linux and macOS")
|
||
|
}
|
||
|
|
||
|
tp.logger.Info("Setting up transparent proxy rules...")
|
||
|
|
||
|
// 备份当前DNS设置
|
||
|
if err := tp.backupDNS(); err != nil {
|
||
|
tp.logger.WithError(err).Warn("Failed to backup DNS settings")
|
||
|
}
|
||
|
|
||
|
switch runtime.GOOS {
|
||
|
case "linux":
|
||
|
return tp.setupLinuxRules()
|
||
|
case "darwin":
|
||
|
return tp.setupMacOSRules()
|
||
|
default:
|
||
|
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CleanupTransparentProxy 清理透明代理规则
|
||
|
func (tp *TransparentProxy) CleanupTransparentProxy() error {
|
||
|
tp.logger.Info("Cleaning up transparent proxy rules...")
|
||
|
|
||
|
var errors []string
|
||
|
|
||
|
// 恢复DNS设置
|
||
|
if err := tp.restoreDNS(); err != nil {
|
||
|
errors = append(errors, fmt.Sprintf("DNS restore: %v", err))
|
||
|
}
|
||
|
|
||
|
switch runtime.GOOS {
|
||
|
case "linux":
|
||
|
if err := tp.cleanupLinuxRules(); err != nil {
|
||
|
errors = append(errors, fmt.Sprintf("Linux rules: %v", err))
|
||
|
}
|
||
|
case "darwin":
|
||
|
if err := tp.cleanupMacOSRules(); err != nil {
|
||
|
errors = append(errors, fmt.Sprintf("macOS rules: %v", err))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(errors) > 0 {
|
||
|
return fmt.Errorf("cleanup errors: %s", strings.Join(errors, "; "))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// setupLinuxRules 设置Linux iptables规则
|
||
|
func (tp *TransparentProxy) setupLinuxRules() error {
|
||
|
rules := []string{
|
||
|
// 创建新的链
|
||
|
"iptables -t nat -N WORMHOLE_OUTPUT",
|
||
|
"iptables -t nat -N WORMHOLE_PREROUTING",
|
||
|
|
||
|
// 跳过本地流量
|
||
|
fmt.Sprintf("iptables -t nat -A WORMHOLE_OUTPUT -d 127.0.0.0/8 -j RETURN"),
|
||
|
fmt.Sprintf("iptables -t nat -A WORMHOLE_OUTPUT -d 10.0.0.0/8 -j RETURN"),
|
||
|
fmt.Sprintf("iptables -t nat -A WORMHOLE_OUTPUT -d 172.16.0.0/12 -j RETURN"),
|
||
|
fmt.Sprintf("iptables -t nat -A WORMHOLE_OUTPUT -d 192.168.0.0/16 -j RETURN"),
|
||
|
|
||
|
// 重定向TCP流量到透明代理
|
||
|
fmt.Sprintf("iptables -t nat -A WORMHOLE_OUTPUT -p tcp -j REDIRECT --to-ports %d", tp.transparentPort),
|
||
|
|
||
|
// 应用规则
|
||
|
"iptables -t nat -A OUTPUT -j WORMHOLE_OUTPUT",
|
||
|
"iptables -t nat -A PREROUTING -j WORMHOLE_PREROUTING",
|
||
|
|
||
|
// DNS重定向
|
||
|
fmt.Sprintf("iptables -t nat -A WORMHOLE_PREROUTING -p udp --dport 53 -j REDIRECT --to-ports %d", tp.dnsPort),
|
||
|
fmt.Sprintf("iptables -t nat -A WORMHOLE_OUTPUT -p udp --dport 53 -j REDIRECT --to-ports %d", tp.dnsPort),
|
||
|
}
|
||
|
|
||
|
return tp.executeRules(rules)
|
||
|
}
|
||
|
|
||
|
// setupMacOSRules 设置macOS pfctl规则
|
||
|
func (tp *TransparentProxy) setupMacOSRules() error {
|
||
|
// macOS需要使用pfctl,配置更复杂
|
||
|
pfRules := fmt.Sprintf(`
|
||
|
# Wormhole transparent proxy rules
|
||
|
rdr pass on lo0 inet proto tcp from any to any port 1:65535 -> 127.0.0.1 port %d
|
||
|
rdr pass on lo0 inet proto udp from any to any port 53 -> 127.0.0.1 port %d
|
||
|
pass out quick proto tcp from any to any flags S/SA keep state
|
||
|
pass out quick proto udp from any to any keep state
|
||
|
`, tp.transparentPort, tp.dnsPort)
|
||
|
|
||
|
// 写入pfctl规则文件
|
||
|
if err := tp.writePfRules(pfRules); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// 加载规则
|
||
|
rules := []string{
|
||
|
"pfctl -f /tmp/wormhole.pf.conf",
|
||
|
"pfctl -e",
|
||
|
}
|
||
|
|
||
|
return tp.executeRules(rules)
|
||
|
}
|
||
|
|
||
|
// GetOriginalDestination 获取透明代理的原始目标地址
|
||
|
func (tp *TransparentProxy) GetOriginalDestination(conn net.Conn) (string, error) {
|
||
|
tcpConn, ok := conn.(*net.TCPConn)
|
||
|
if !ok {
|
||
|
return "", fmt.Errorf("not a TCP connection")
|
||
|
}
|
||
|
|
||
|
// 获取socket文件描述符
|
||
|
file, err := tcpConn.File()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
defer file.Close()
|
||
|
|
||
|
fd := int(file.Fd())
|
||
|
|
||
|
switch runtime.GOOS {
|
||
|
case "linux":
|
||
|
return tp.getLinuxOriginalDest(fd)
|
||
|
case "darwin":
|
||
|
return tp.getMacOSOriginalDest(fd)
|
||
|
default:
|
||
|
return "", fmt.Errorf("unsupported OS for transparent proxy")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) executeRules(rules []string) error {
|
||
|
for _, rule := range rules {
|
||
|
tp.rules = append(tp.rules, rule)
|
||
|
|
||
|
parts := strings.Fields(rule)
|
||
|
if len(parts) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
cmd := exec.Command(parts[0], parts[1:]...)
|
||
|
if output, err := cmd.CombinedOutput(); err != nil {
|
||
|
tp.logger.WithFields(logrus.Fields{
|
||
|
"rule": rule,
|
||
|
"output": string(output),
|
||
|
}).WithError(err).Error("Failed to execute rule")
|
||
|
// 继续执行其他规则,不要因为一个失败就停止
|
||
|
} else {
|
||
|
tp.logger.WithField("rule", rule).Debug("Rule executed successfully")
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) cleanupLinuxRules() error {
|
||
|
cleanupRules := []string{
|
||
|
"iptables -t nat -D OUTPUT -j WORMHOLE_OUTPUT",
|
||
|
"iptables -t nat -D PREROUTING -j WORMHOLE_PREROUTING",
|
||
|
"iptables -t nat -F WORMHOLE_OUTPUT",
|
||
|
"iptables -t nat -F WORMHOLE_PREROUTING",
|
||
|
"iptables -t nat -X WORMHOLE_OUTPUT",
|
||
|
"iptables -t nat -X WORMHOLE_PREROUTING",
|
||
|
}
|
||
|
|
||
|
return tp.executeRules(cleanupRules)
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) cleanupMacOSRules() error {
|
||
|
rules := []string{
|
||
|
"pfctl -d",
|
||
|
"rm -f /tmp/wormhole.pf.conf",
|
||
|
}
|
||
|
|
||
|
return tp.executeRules(rules)
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) backupDNS() error {
|
||
|
// 这里简化处理,实际应该备份/etc/resolv.conf
|
||
|
tp.originalDNS = []string{"8.8.8.8", "8.8.4.4"}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) restoreDNS() error {
|
||
|
// 恢复DNS设置
|
||
|
tp.logger.Info("DNS settings restored")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) writePfRules(rules string) error {
|
||
|
return exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > /tmp/wormhole.pf.conf", rules)).Run()
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) getLinuxOriginalDest(fd int) (string, error) {
|
||
|
// Linux SO_ORIGINAL_DST
|
||
|
const SO_ORIGINAL_DST = 80
|
||
|
|
||
|
_, err := syscall.GetsockoptIPv6Mreq(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
// 解析地址结构(这里简化处理)
|
||
|
return "unknown:80", nil
|
||
|
}
|
||
|
|
||
|
func (tp *TransparentProxy) getMacOSOriginalDest(fd int) (string, error) {
|
||
|
// macOS implementation would be more complex
|
||
|
return "unknown:80", nil
|
||
|
}
|
||
|
|
||
|
// IsTransparentSupported 检查系统是否支持透明代理
|
||
|
func IsTransparentSupported() bool {
|
||
|
switch runtime.GOOS {
|
||
|
case "linux":
|
||
|
// 检查是否有iptables
|
||
|
if _, err := exec.LookPath("iptables"); err != nil {
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
case "darwin":
|
||
|
// 检查是否有pfctl
|
||
|
if _, err := exec.LookPath("pfctl"); err != nil {
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RequiresRoot 检查是否需要root权限
|
||
|
func RequiresRoot() bool {
|
||
|
return runtime.GOOS == "linux" || runtime.GOOS == "darwin"
|
||
|
}
|