Skip to content

Commit

Permalink
优化 在丢包等网络不佳时或握手消息分段时确保能收到完整握手消息
Browse files Browse the repository at this point in the history
  • Loading branch information
XIU2 committed Sep 1, 2024
1 parent 69c27cb commit 2d613e8
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 20 deletions.
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,12 @@ mkdir sniproxy
cd sniproxy

# 下载 sniproxy 压缩包(自行根据需求替换 URL 中 [版本号] 和 [文件名])
wget -N https://github.com/XIU2/SNIProxy/releases/download/v1.0.1/sniproxy_linux_amd64.tar.gz
wget -N https://github.com/XIU2/SNIProxy/releases/download/v1.0.2/sniproxy_linux_amd64.tar.gz
# 如果你是在国内服务器上下载,那么请使用下面这几个镜像加速:
# wget -N https://download.scholar.rr.nu/XIU2/SNIProxy/releases/download/v1.0.1/sniproxy_linux_amd64.tar.gz
# wget -N https://ghproxy.cc/https://github.com/XIU2/SNIProxy/releases/download/v1.0.1/sniproxy_linux_amd64.tar.gz
# wget -N https://ghproxy.net/https://github.com/XIU2/SNIProxy/releases/download/v1.0.1/sniproxy_linux_amd64.tar.gz
# wget -N https://gh-proxy.com/https://github.com/XIU2/SNIProxy/releases/download/v1.0.1/sniproxy_linux_amd64.tar.gz
# wget -N https://mirror.ghproxy.com/https://github.com/XIU2/SNIProxy/releases/download/v1.0.1/sniproxy_linux_amd64.tar.gz
# wget -N https://ghproxy.cc/https://github.com/XIU2/SNIProxy/releases/download/v1.0.2/sniproxy_linux_amd64.tar.gz
# wget -N https://ghproxy.net/https://github.com/XIU2/SNIProxy/releases/download/v1.0.2/sniproxy_linux_amd64.tar.gz
# wget -N https://gh-proxy.com/https://github.com/XIU2/SNIProxy/releases/download/v1.0.2/sniproxy_linux_amd64.tar.gz
# wget -N https://mirror.ghproxy.com/https://github.com/XIU2/SNIProxy/releases/download/v1.0.2/sniproxy_linux_amd64.tar.gz

# 如果下载失败的话,尝试删除 -N 参数(如果是为了更新,则记得提前删除旧压缩包 rm sniproxy_linux_amd64.tar.gz )

Expand Down Expand Up @@ -419,8 +418,8 @@ systemctl restart sniproxy
为了方便,我是在编译的时候将版本号写入代码中的 version 变量,因此你手动编译时,需要像下面这样在 `go build` 命令后面加上 `-ldflags` 参数来指定版本号:

```bash
go build -ldflags "-s -w -X main.version=v1.0.1"
# 在 SNIProxy 目录中通过命令行(例如 CMD、Bat 脚本)运行该命令,即可编译一个可在和当前设备同样系统、位数、架构的环境下运行的二进制程序(Go 会自动检测你的系统位数、架构)且版本号为 v1.0.1
go build -ldflags "-s -w -X main.version=v1.0.2"
# 在 SNIProxy 目录中通过命令行(例如 CMD、Bat 脚本)运行该命令,即可编译一个可在和当前设备同样系统、位数、架构的环境下运行的二进制程序(Go 会自动检测你的系统位数、架构)且版本号为 v1.0.2
```

如果想要在 Windows 64位系统下编译**其他系统、架构、位数**,那么需要指定 **GOOS****GOARCH** 变量。
Expand All @@ -430,15 +429,15 @@ go build -ldflags "-s -w -X main.version=v1.0.1"
```bat
SET GOOS=linux
SET GOARCH=amd64
go build -ldflags "-s -w -X main.version=v1.0.1"
go build -ldflags "-s -w -X main.version=v1.0.2"
```

例如在 Linux 系统下编译一个适用于 **Windows 系统 amd 架构 32 位**的二进制程序:

```bash
GOOS=windows
GOARCH=386
go build -ldflags "-s -w -X main.version=v1.0.1"
go build -ldflags "-s -w -X main.version=v1.0.2"
```

> 可以运行 `go tool dist list` 来查看当前 Go 版本支持编译哪些组合。
Expand All @@ -450,15 +449,15 @@ go build -ldflags "-s -w -X main.version=v1.0.1"

```bat
:: Windows 系统下是这样:
SET version=v1.0.1
SET version=v1.0.2
SET GOOS=linux
SET GOARCH=amd64
go build -o Releases\sniproxy_linux_amd64\sniproxy -ldflags "-s -w -X main.version=%version%"
```

```bash
# Linux 系统下是这样:
version=v1.0.1
version=v1.0.2
GOOS=windows
GOARCH=386
go build -o Releases/sniproxy_windows_386/sniproxy.exe -ldflags "-s -w -X main.version=${version}"
Expand Down
49 changes: 41 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"encoding/binary"
"flag"
"fmt"
"io"
Expand Down Expand Up @@ -124,14 +125,36 @@ func startSniProxy() {
func serve(c net.Conn, raddr string) {
defer c.Close()

buf := make([]byte, 2048) // 分配缓冲区
n, err := c.Read(buf) // 读入新连接的内容
if err != nil && fmt.Sprintf("%v", err) != "EOF" {
serviceLogger(fmt.Sprintf("读取连接请求时出错: %v", err), 31, false)
return
buf := make([]byte, 2048)
var fullHeader []byte
const maxAttempts = 5 // 最大等待次数
attempts := 0

for {
c.SetReadDeadline(time.Now().Add(1500 * time.Millisecond)) // 设置每次循环中读取的超时时间
n, err := c.Read(buf)
if err != nil && fmt.Sprintf("%v", err) != "EOF" {
serviceLogger(fmt.Sprintf("读取连接请求时出错: %v", err), 31, false)
return
}
fullHeader = append(fullHeader, buf[:n]...)

// 检查是否已经接收到完整的 TLS 握手消息
if IsCompleteHandshake(fullHeader) {
break // 如果已经完整,那么退出循环
}

attempts++
if attempts >= maxAttempts {
serviceLogger("接收握手消息超时...", 31, false)
return // 如果超过最大等待次数,退出函数
}
}
if attempts > 0 { // 如果等于 0 说明第一次就接收到了完整握手消息,如果大于 0 说明握手消息丢包或分段了
serviceLogger("已接收到完整握手消息...", 32, true)
}

ServerName := getSNIServerName(buf[:n]) // 获取 SNI 域名
ServerName := getSNIServerName(fullHeader) // 获取 SNI 域名

if ServerName == "" {
serviceLogger("未找到 SNI 域名, 忽略...", 31, true)
Expand All @@ -140,18 +163,28 @@ func serve(c net.Conn, raddr string) {

if cfg.AllowAllHosts { // 如果 allow_all_hosts 为 true 则代表无需判断 SNI 域名
serviceLogger(fmt.Sprintf("转发目标: %s:%d", ServerName, ForwardPort), 32, false)
forward(c, buf[:n], fmt.Sprintf("%s:%d", ServerName, ForwardPort), raddr)
forward(c, fullHeader, fmt.Sprintf("%s:%d", ServerName, ForwardPort), raddr)
return
}

for _, rule := range cfg.ForwardRules { // 循环遍历 Rules 中指定的白名单域名
if strings.Contains(ServerName, rule) { // 如果 SNI 域名中包含 Rule 白名单域名(例如 www.aa.com 中包含 aa.com)则转发该连接
serviceLogger(fmt.Sprintf("转发目标: %s:%d", ServerName, ForwardPort), 32, false)
forward(c, buf[:n], fmt.Sprintf("%s:%d", ServerName, ForwardPort), raddr)
forward(c, fullHeader, fmt.Sprintf("%s:%d", ServerName, ForwardPort), raddr)
}
}
}

// 判断握手消息是否完整
func IsCompleteHandshake(header []byte) bool {
if len(header) < 5 {
return false
}
Length := binary.BigEndian.Uint16(header[3:5])
//serviceLogger(fmt.Sprintf("长度检查: %d:%d", len(header), int(Length)+5), 32, true)
return len(header) >= int(Length)+5
}

// 获取 SNI 域名
func getSNIServerName(buf []byte) string {
n := len(buf)
Expand Down

0 comments on commit 2d613e8

Please sign in to comment.