package main import ( "bufio" "context" "encoding/binary" "github.com/joho/godotenv" "github.com/pkg/errors" "io" "log/slog" "net" "os" "proxy-server/pkg/socks5" "proxy-server/pkg/utils" "proxy-server/server/orm" "proxy-server/server/web/app/models" "strconv" "time" ) var connMap = make(map[string]socks5.ProxyData) func main() { slog.SetLogLoggerLevel(slog.LevelDebug) // 初始化环境变量 err := godotenv.Load() if err != nil { slog.Debug("没有本地环境变量文件") } orm.Init() go startControlTun() go startDataTun() select {} } func startDataTun() { dataPort := os.Getenv("APP_DATA_PORT") if dataPort == "" { panic("环境变量 APP_DATA_PORT 未设置") } slog.Info("监听数据端口 " + dataPort) lData, err := net.Listen("tcp", ":"+dataPort) if err != nil { slog.Error("listen error", err) return } defer lData.Close() for { conn, err := lData.Accept() if err != nil { slog.Error("accept error", err) return } processDataConn(conn) } } func processDataConn(client net.Conn) { slog.Info("已建立客户端数据通道 " + client.RemoteAddr().String()) // 读取 tag tagLen, err := utils.ReadByte(client) if err != nil { slog.Error("read error", err) return } tagBuf, err := utils.ReadBuffer(client, int(tagLen)) if err != nil { slog.Error("read error", err) return } tag := string(tagBuf) // 找到用户连接 data, ok := connMap[tag] if !ok { slog.Error("no such connection") return } // 响应用户 user := data.Conn socks5.SendSuccess(user, client) // 写入目标地址 _, err = client.Write([]byte{byte(len(data.Dest))}) if err != nil { slog.Error("写入目标地址失败", err) return } _, err = client.Write([]byte(data.Dest)) if err != nil { slog.Error("写入目标地址失败", err) return } // 数据转发 slog.Info("开始数据转发 " + client.RemoteAddr().String() + " <-> " + data.Dest) errCh := make(chan error) go func() { _, err := io.Copy(client, user) if err != nil { slog.Error("processDataConn error c2u", err) } errCh <- err }() go func() { _, err := io.Copy(user, client) if err != nil { slog.Error("processDataConn error u2c", err) } errCh <- err }() <-errCh slog.Info("数据转发结束 " + client.RemoteAddr().String() + " <-> " + data.Dest) defer func() { err := user.Close() if err != nil { slog.Error("close error", err) } err = client.Close() if err != nil { slog.Error("close error", err) } }() } func startControlTun() { ctrlPort := os.Getenv("APP_CTRL_PORT") if ctrlPort == "" { panic("环境变量 APP_CTRL_PORT 未设置") } slog.Info("监听控制端口 " + ctrlPort) lControl, err := net.Listen("tcp", ":"+ctrlPort) if err != nil { slog.Error("listen error", err) return } defer lControl.Close() for { conn, err := lControl.Accept() if err != nil { slog.Error("accept error", err) return } go processController(conn) } } func processController(controller net.Conn) { defer controller.Close() slog.Info("收到客户端控制连接 " + controller.RemoteAddr().String()) reader := bufio.NewReader(controller) // 读取端口 portBuf := make([]byte, 2) _, err := io.ReadFull(reader, portBuf) if err != nil { slog.Error("读取转发端口失败", "err", err) return } port := binary.BigEndian.Uint16(portBuf) // 新建代理服务 slog.Info("新建代理服务", "port", port) proxy, err := socks5.New(&socks5.Config{ Name: strconv.Itoa(int(port)), Port: port, AuthMethods: []socks5.Authenticator{ &UserPassAuthenticator{}, &NoAuthAuthenticator{}, }, }) if err != nil { slog.Error("代理服务创建失败", err) return } go func() { err := proxy.Run() if err != nil { slog.Error("代理服务建立失败", err) return } }() slog.Info("代理服务已建立", "port", port) for { user := <-proxy.Conn tag := user.Tag() _, err := controller.Write([]byte{byte(len(tag))}) if err != nil { slog.Error("write error", err) return } _, err = controller.Write([]byte(tag)) slog.Info("已通知客户端建立数据通道") if err != nil { slog.Error("write error", err) return } connMap[tag] = user } } type NoAuthAuthenticator struct { } func (a *NoAuthAuthenticator) Method() socks5.AuthMethod { return socks5.NoAuth } func (a *NoAuthAuthenticator) Authenticate(ctx context.Context, reader io.Reader, writer io.Writer) (*socks5.AuthContext, error) { // 检查用户是否在白名单中,如果不在则返回错误 conn, ok := writer.(net.Conn) if !ok { return nil, errors.New("noAuth 认证失败,无法获取连接信息") } addr := conn.RemoteAddr().String() client, _, err := net.SplitHostPort(addr) if err != nil { return nil, errors.Wrap(err, "noAuth 认证失败") } slog.Info("用户的地址为 " + client) // 检查此 ip 是否有权限访问目标 node server, ok := ctx.Value("service").(*socks5.Server) if !ok { return nil, errors.New("noAuth 认证失败,无法获取服务信息") } node := server.Name slog.Debug(" 客户端 " + client + " 请求连接到 " + node) var channels []models.Channel err = orm.DB. Joins("INNER JOIN public.nodes n ON channels.node_id = n.id AND n.name = ?", node). Joins("INNER JOIN public.users u ON channels.user_id = u.id"). Joins("INNER JOIN public.user_ips ip ON u.id = ip.user_id AND ip.ip_address = ?", client). Find(&channels).Error if err != nil { return nil, errors.New("noAuth 认证失败,查询用户权限失败") } if len(channels) == 0 { return nil, errors.New("noAuth 认证失败,没有权限") } if len(channels) > 1 { slog.Warn(client + " + " + node + "的组合有多个权限结果,这是不应当存在的") } channel := channels[0] if !channel.AuthIp { return nil, errors.New("noAuth 认证失败,没有权限") } if channel.Expiration.Before(time.Now()) { return nil, errors.New("noAuth 认证失败,权限已过期") } return &socks5.AuthContext{ Method: socks5.NoAuth, Timeout: 300, // todo Payload: nil, }, nil } type UserPassAuthenticator struct { } func (a *UserPassAuthenticator) Method() socks5.AuthMethod { return socks5.UserPassAuth } func (a *UserPassAuthenticator) Authenticate(ctx context.Context, reader io.Reader, writer io.Writer) (*socks5.AuthContext, error) { // 检查版本 v, err := utils.ReadByte(reader) if err != nil { return nil, errors.Wrap(err, "读取版本号失败") } if v != socks5.AuthVersion { _, err := writer.Write([]byte{socks5.SocksVersion, socks5.AuthFailure}) if err != nil { return nil, errors.Wrap(err, "响应认证失败") } return nil, errors.New("认证版本参数不正确") } // 读取账号 uLen, err := utils.ReadByte(reader) if err != nil { return nil, errors.Wrap(err, "读取用户名长度失败") } usernameBuf, err := utils.ReadBuffer(reader, int(uLen)) if err != nil { return nil, errors.Wrap(err, "读取用户名失败") } username := string(usernameBuf) // 读取密码 pLen, err := utils.ReadByte(reader) if err != nil { return nil, errors.Wrap(err, "读取密码长度失败") } passwordBuf, err := utils.ReadBuffer(reader, int(pLen)) if err != nil { return nil, errors.Wrap(err, "读取密码失败") } password := string(passwordBuf) var channels []models.Channel err = orm.DB. Where(&models.Channel{ Username: username, }). Find(&channels).Error if err != nil { return nil, errors.Wrap(err, "查询用户失败") } if len(channels) == 0 { return nil, errors.New("没有权限") } if len(channels) > 1 { slog.Warn("用户有多个权限结果,这是不应当存在的") } channel := channels[0] if !channel.AuthPass { return nil, errors.New("没有权限") } if channel.Expiration.Before(time.Now()) { return nil, errors.New("权限已过期") } // 检查密码 todo 哈希 if channel.Password != password { return nil, errors.New("密码错误") } // 如果用户设置了双验证则检查 ip 是否在白名单中 if channel.AuthIp { conn, ok := writer.(net.Conn) if !ok { return nil, errors.New("无法获取连接信息") } addr := conn.RemoteAddr().String() client, _, err := net.SplitHostPort(addr) if err != nil { return nil, errors.Wrap(err, "无法获取连接信息") } slog.Info("用户的地址为 " + client) var ips []models.UserIp err = orm.DB. Where(&models.UserIp{ UserId: channel.UserId, IpAddress: client, }). Find(&ips).Error if err != nil { return nil, errors.Wrap(err, "查询用户 ip 失败") } if len(ips) == 0 { return nil, errors.New("没有权限") } } _, err = writer.Write([]byte{socks5.AuthVersion, socks5.AuthSuccess}) if err != nil { slog.Error("响应认证失败", err) return nil, err } return &socks5.AuthContext{ Method: socks5.UserPassAuth, Timeout: 300, // todo Payload: nil, }, nil }