This commit is contained in:
aixiao 2025-02-07 10:32:47 +08:00
commit 874d625f2d
10 changed files with 518 additions and 0 deletions

BIN
4to6-linux-amd64 Normal file

Binary file not shown.

BIN
4to6-linux-arm64 Normal file

Binary file not shown.

9
4to6.conf Normal file
View File

@ -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
}

8
build.sh Normal file
View File

@ -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"'

139
config.go Normal file
View File

@ -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
}

51
daemon.go Normal file
View File

@ -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)
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module 4to6
go 1.23.5

47
main.go Normal file
View File

@ -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) // 启动所有代理服务
}

137
tcp.go Normal file
View File

@ -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)
}

124
udp.go Normal file
View File

@ -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 代理已关闭")
}