commit 874d625f2d511d4a9f30f2d3bb65896413019df0 Author: aixiao Date: Fri Feb 7 10:32:47 2025 +0800 init diff --git a/4to6-linux-amd64 b/4to6-linux-amd64 new file mode 100644 index 0000000..7da2b07 Binary files /dev/null and b/4to6-linux-amd64 differ diff --git a/4to6-linux-arm64 b/4to6-linux-arm64 new file mode 100644 index 0000000..63ed539 Binary files /dev/null and b/4to6-linux-arm64 differ diff --git a/4to6.conf b/4to6.conf new file mode 100644 index 0000000..7d0d64f --- /dev/null +++ b/4to6.conf @@ -0,0 +1,9 @@ +global { + // 配置多个端口转发规则, 键是监听地址(IPv4), 值为目标地址(可以是IPv6). + + // TCP 转发规则 + LT = 0.0.0.0:2222 -> [2408:8221:9901:8895:4c32:7173:b476:b65c]:22 + + // UDP 转发规则 + LU = 0.0.0.0:53 -> v6.aixiao.me:53 +} diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..2406d5c --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -x + +BIN=4to6 + + +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ${BIN}-linux-amd64 -a -ldflags '-extldflags "-static"' +CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o ${BIN}-linux-arm64 -a -ldflags '-extldflags "-static"' diff --git a/config.go b/config.go new file mode 100644 index 0000000..1f6eba7 --- /dev/null +++ b/config.go @@ -0,0 +1,139 @@ +package main + +import ( + "bufio" + "fmt" + "net" + "os" + "strconv" + "strings" +) + +// Config 结构体存储配置信息 +type Config struct { + Global struct { + LT map[string]string // 存储本地地址到目标地址的映射 + LU map[string]string // 存储本地地址到目标地址的映射 + } +} + +// isValidHostname 验证主机名格式 +func isValidHostname(hostname string) bool { + if len(hostname) > 255 || hostname == "" { + return false + } + for _, part := range strings.Split(hostname, ".") { + if len(part) == 0 || len(part) > 63 { + return false + } + for _, c := range part { + if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-') { + return false + } + } + } + return true +} + +// parseAddress 解析并验证地址字符串 +func parseAddress(address string) (string, int, error) { + host, portStr, err := net.SplitHostPort(address) + if err != nil { + return "", 0, fmt.Errorf("地址格式错误: %s (格式应为 host:port)", address) + } + + port, err := strconv.Atoi(portStr) + if err != nil || port < 1 || port > 65535 { + return "", 0, fmt.Errorf("无效的端口号: %s", address) + } + + host = strings.Trim(host, "[]") // 去除 IPv6 方括号 + if net.ParseIP(host) == nil && !isValidHostname(host) { + return "", 0, fmt.Errorf("无效的主机名或 IP: %s", address) + } + + return host, port, nil +} + +// parseConfig 解析配置文件 +func parseConfig(filename string) (Config, error) { + var config Config + config.Global.LT = make(map[string]string) + config.Global.LU = make(map[string]string) + var currentSection string + + file, err := os.Open(filename) + if err != nil { + return config, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "#") { + continue + } + + if strings.HasSuffix(line, "{") { + currentSection = strings.Fields(line)[0] + continue + } + if line == "}" { + currentSection = "" + continue + } + + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(strings.TrimSuffix(parts[1], ";")) + + if currentSection == "global" && key == "LT" { + lsParts := strings.SplitN(value, "->", 2) + if len(lsParts) == 2 { + localAddr := strings.TrimSpace(lsParts[0]) + targetAddr := strings.TrimSpace(lsParts[1]) + + if _, _, err := parseAddress(localAddr); err != nil { + return config, fmt.Errorf("本地地址格式错误: %s", localAddr) + } + if _, _, err := parseAddress(targetAddr); err != nil { + return config, fmt.Errorf("目标地址格式错误: %s", targetAddr) + } + + config.Global.LT[localAddr] = targetAddr + } else { + return config, fmt.Errorf("LT 配置格式错误: %s", value) + } + } + + if currentSection == "global" && key == "LU" { + lsParts := strings.SplitN(value, "->", 2) + if len(lsParts) == 2 { + localAddr := strings.TrimSpace(lsParts[0]) + targetAddr := strings.TrimSpace(lsParts[1]) + + if _, _, err := parseAddress(localAddr); err != nil { + return config, fmt.Errorf("本地地址格式错误: %s", localAddr) + } + if _, _, err := parseAddress(targetAddr); err != nil { + return config, fmt.Errorf("目标地址格式错误: %s", targetAddr) + } + + config.Global.LU[localAddr] = targetAddr + } else { + return config, fmt.Errorf("LU 配置格式错误: %s", value) + } + } + } + + if err := scanner.Err(); err != nil { + return config, err + } + + return config, nil +} diff --git a/daemon.go b/daemon.go new file mode 100644 index 0000000..bbfab51 --- /dev/null +++ b/daemon.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" +) + +// Daemon 函数用于将程序转换为守护进程模式(简易实现) +// 注意:这不是 POSIX 标准的完整守护进程实现,缺少以下标准步骤: +// 1. 未创建新会话(setsid) +// 2. 未重置文件权限掩码 +// 3. 未改变工作目录 +// 4. 未关闭/重定向标准输入输出 +// 适用于简单后台需求,生产环境建议使用专业库 +func Daemon() { + // 构建新的命令行参数(过滤掉 -d 参数) + args := make([]string, 0) + for _, arg := range os.Args[1:] { // 遍历原始参数(跳过第一个程序路径参数) + if arg != "-d" { // 过滤掉用于触发守护进程的 -d 标志 + args = append(args, arg) + } + } + + // 创建新的进程命令 + // os.Args[0] 是当前程序的执行路径 + // args 是过滤后的新参数列表(不包含 -d) + cmd := exec.Command(os.Args[0], args...) + + // 配置标准输入输出(注意:真正的守护进程通常需要重定向这些) + // 这里继承父进程的标准输入输出,实际使用时可能需要: + // 1. 重定向到文件 + // 2. 指向 /dev/null + // 3. 使用日志系统 + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + // 异步启动新进程(Start 而非 Run) + // 注意:此处未设置新进程组或会话,可能导致子进程随终端关闭 + if err := cmd.Start(); err != nil { + log.Fatalf("无法启动新实例: %v", err) // 启动失败时终止父进程 + } + + // 输出提示信息(调试用,正式版可删除) + fmt.Println("新实例已启动,当前进程将退出") + + // 退出父进程,让子进程继续运行 + // 注意:此时子进程的父进程变为 init 进程(PID 1) + os.Exit(0) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bbd3aa0 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module 4to6 + +go 1.23.5 diff --git a/main.go b/main.go new file mode 100644 index 0000000..4b4b721 --- /dev/null +++ b/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" +) + +// 主循环,为每个端口映射启动一个代理 +func Loop(config Config) { + ctx, cancel := context.WithCancel(context.Background()) + + // 启动 TCP 代理 + for tcp_listenAddr, tcp_targetAddr := range config.Global.LT { + tcp_wg.Add(1) + go StartTcpProxy(tcp_listenAddr, tcp_targetAddr) + } + + // 启动 UDP 代理 + for udp_listenAddr, udp_targetAddr := range config.Global.LU { + udp_wg.Add(1) + go StartUdpProxy(ctx, udp_listenAddr, udp_targetAddr) + } + + // 监听退出信号,触发 `cancel()` 让所有 Goroutine 退出 + WaitForExit(cancel) +} + +func main() { + daemon := flag.Bool("d", false, "守护进程模式") // 解析命令行参数,是否以守护进程模式运行 + config := flag.String("c", "4to6.conf", "指定配置文件") // 解析命令行参数,是否以守护进程模式运行 + flag.Parse() + + if *daemon { + Daemon() // 如果设置了-d参数,则进入守护进程模式 + } + + LT, err := parseConfig(*config) + if err != nil { + fmt.Fprintf(os.Stderr, "读取配置错误: %v\n", err) + os.Exit(1) + } + + Loop(LT) // 启动所有代理服务 + +} diff --git a/tcp.go b/tcp.go new file mode 100644 index 0000000..731955e --- /dev/null +++ b/tcp.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + "os/signal" + "sync" + "syscall" + "time" +) + +var ( + tcp_wg sync.WaitGroup + tcp_listeners []net.Listener + tcp_listenMux sync.Mutex // 确保 tcp_listeners 操作是线程安全的 +) + +// 启用 TCP KeepAlive +func setKeepAlive(conn net.Conn) { + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(30 * time.Second) + } +} + +// 关闭写方向 +func closeWrite(conn net.Conn) { + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.CloseWrite() + } +} + +// 处理客户端连接,并将数据转发到目标服务器 +func HandleTcpConnection(clientConn net.Conn, targetAddr string) { + defer clientConn.Close() + + // 连接目标服务器,支持 IPv6 + serverConn, err := net.Dial("tcp", targetAddr) + if err != nil { + fmt.Printf("无法连接到 %s: %v\n", targetAddr, err) + return + } + defer serverConn.Close() + + // 开启 TCP KeepAlive,防止连接过早断开 + setKeepAlive(clientConn) + setKeepAlive(serverConn) + + clientAddr := clientConn.RemoteAddr().String() + fmt.Printf("连接 %s -> %s\n", clientAddr, targetAddr) + + var cpWG sync.WaitGroup + cpWG.Add(2) + + // 客户端 -> 目标服务器 + go func() { + defer cpWG.Done() + _, err := io.Copy(serverConn, clientConn) + if err != nil && !errors.Is(err, io.EOF) { + fmt.Printf("数据转发 %s -> %s 失败: %v\n", clientAddr, targetAddr, err) + } + closeWrite(serverConn) + }() + + // 目标服务器 -> 客户端 + go func() { + defer cpWG.Done() + _, err := io.Copy(clientConn, serverConn) + if err != nil && !errors.Is(err, io.EOF) { + fmt.Printf("数据转发 %s <- %s 失败: %v\n", clientAddr, targetAddr, err) + } + closeWrite(clientConn) + }() + + cpWG.Wait() + fmt.Printf("连接 %s 结束\n", clientAddr) +} + +// 启动代理服务器 +func StartTcpProxy(listenAddr, targetAddr string) { + defer tcp_wg.Done() + + listener, err := net.Listen("tcp", listenAddr) + if err != nil { + fmt.Printf("监听 %s 失败: %v\n", listenAddr, err) + return + } + + tcp_listenMux.Lock() + tcp_listeners = append(tcp_listeners, listener) + tcp_listenMux.Unlock() + + fmt.Printf("代理服务启动: %s -> %s\n", listenAddr, targetAddr) + + for { + clientConn, err := listener.Accept() + if err != nil { + if errors.Is(err, net.ErrClosed) { + fmt.Printf("监听器 %s 已关闭\n", listenAddr) + return + } + fmt.Printf("接受连接失败: %v\n", err) + continue + } + go HandleTcpConnection(clientConn, targetAddr) + } +} + +// 监听系统信号,以便优雅地关闭 TCP 和 UDP 代理服务 +func WaitForExit(cancel context.CancelFunc) { + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + <-sigChan + fmt.Println("\n收到终止信号,准备关闭代理服务器...") + + // 关闭所有 TCP 监听器 + for _, listener := range tcp_listeners { + if err := listener.Close(); err != nil { + fmt.Printf("关闭 TCP 监听器 %s 失败: %v\n", listener.Addr(), err) + } + } + + // 触发 UDP 代理退出 + cancel() + + // 等待所有 TCP 和 UDP 代理退出 + tcp_wg.Wait() + udp_wg.Wait() + + fmt.Println("所有代理已安全退出") + os.Exit(0) +} diff --git a/udp.go b/udp.go new file mode 100644 index 0000000..d8c2afd --- /dev/null +++ b/udp.go @@ -0,0 +1,124 @@ +package main + +import ( + "context" + "fmt" + "net" + "sync" + "time" +) + +var ( + udp_wg sync.WaitGroup + clientMap sync.Map // 代替 sync.Mutex 保护的 map[string]*net.UDPConn +) + +// 处理 UDP 数据转发 +func handleUdpTraffic(listener *net.UDPConn, targetConn *net.UDPConn, clientAddr *net.UDPAddr, data []byte) { + // 目标服务器超时设置,防止阻塞 + targetConn.SetWriteDeadline(time.Now().Add(2 * time.Second)) + _, err := targetConn.Write(data) + if err != nil { + fmt.Printf("UDP 数据转发失败 %s -> %s: %v\n", clientAddr, targetConn.RemoteAddr(), err) + return + } + + // 读取目标服务器响应 + targetConn.SetReadDeadline(time.Now().Add(2 * time.Second)) + buffer := make([]byte, 4096) + n, _, err := targetConn.ReadFromUDP(buffer) + if err != nil { + fmt.Printf("UDP 读取目标服务器响应失败: %v\n", err) + return + } + + // 发送响应回客户端 + listener.SetWriteDeadline(time.Now().Add(2 * time.Second)) + _, err = listener.WriteToUDP(buffer[:n], clientAddr) + if err != nil { + fmt.Printf("UDP 发送响应失败: %v\n", err) + } +} + +// 启动 UDP 代理 +func StartUdpProxy(ctx context.Context, listenAddr, targetAddr string) { + defer udp_wg.Done() + + localAddr, err := net.ResolveUDPAddr("udp", listenAddr) + if err != nil { + fmt.Printf("解析本地 UDP 地址 %s 失败: %v\n", listenAddr, err) + return + } + + listener, err := net.ListenUDP("udp", localAddr) + if err != nil { + fmt.Printf("监听 UDP %s 失败: %v\n", listenAddr, err) + return + } + defer listener.Close() + + targetUDPAddr, err := net.ResolveUDPAddr("udp", targetAddr) + if err != nil { + fmt.Printf("解析目标 UDP 地址 %s 失败: %v\n", targetAddr, err) + return + } + + fmt.Printf("UDP 代理启动: %s -> %s\n", listenAddr, targetAddr) + + // 设置多个 goroutine 处理 UDP 请求,提高吞吐量 + numWorkers := 4 // 线程数,可根据 CPU 资源调整 + for i := 0; i < numWorkers; i++ { + udp_wg.Add(1) + go func() { + defer udp_wg.Done() + buffer := make([]byte, 4096) + + for { + select { + case <-ctx.Done(): + return + default: + listener.SetReadDeadline(time.Now().Add(1 * time.Second)) + n, clientAddr, err := listener.ReadFromUDP(buffer) + if err != nil { + if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { + continue + } + if ctx.Err() != nil { + return + } + fmt.Printf("UDP 读取数据失败: %v\n", err) + continue + } + + // 查找或创建目标连接 + clientKey := clientAddr.String() + targetConn, exists := clientMap.Load(clientKey) + if !exists { + targetConn, err = net.DialUDP("udp", nil, targetUDPAddr) + if err != nil { + fmt.Printf("连接 UDP 目标 %s 失败: %v\n", targetAddr, err) + continue + } + clientMap.Store(clientKey, targetConn) + } + + // 处理 UDP 数据 + go handleUdpTraffic(listener, targetConn.(*net.UDPConn), clientAddr, buffer[:n]) + } + } + }() + } + + // 等待退出信号 + <-ctx.Done() + fmt.Println("\n收到退出信号,正在关闭 UDP 代理...") + + listener.Close() + clientMap.Range(func(key, value interface{}) bool { + value.(*net.UDPConn).Close() + return true + }) + + fmt.Println("UDP 代理已关闭") +}