mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
merge dev into main for v2.2.0-rc.1 re-release
This commit is contained in:
@@ -8,34 +8,104 @@
|
||||
|
||||
## 与 v2.2.0-rc 的变更
|
||||
|
||||
本版本聚焦**实机测试发现的可靠性问题修复**,无新功能。
|
||||
本版本基于大量实机测试反馈,**修复 30+ 个问题,新增自适应扫描系统**。183 个文件变更。
|
||||
|
||||
---
|
||||
|
||||
### 🚀 新功能
|
||||
|
||||
#### 自适应并发调度
|
||||
|
||||
扫描前自动探测网络环境(RTT、丢包率、fd limit),基于探测数据推导关键参数,替代硬编码默认值:
|
||||
|
||||
- **Timeout**: `median_RTT + 4σ`(覆盖 99.9% 正常连接),下限 1s,上限 10s
|
||||
- **ModuleThreadNum**: `ThreadNum / 30`,下限 5,上限 50
|
||||
- **MaxRetries**: 基于丢包率推导,保证全失败概率 <1%
|
||||
- **ICMPRate / PocNum**: 跟随环境和并发自动调整
|
||||
|
||||
线程池升级为 **AIMD + 慢启动**:慢启动阶段 500ms 翻倍,稳态 AIMD(健康 +5%,拥塞 ×0.5),双信号(资源耗尽率 + RTT 趋势)驱动。
|
||||
|
||||
#### 协议级超时下限(ModuleTimeout)
|
||||
|
||||
新增 `Config.ModuleTimeout()` 方法,保证插件级交互超时不低于 3s。自适应系统将端口扫描超时压到 1s 时,SSH 握手、SNMP 探测、数据库认证等多轮交互协议不再受影响。全部 44 个服务插件已迁移。
|
||||
|
||||
#### 限流错误分类(ErrorTypeThrottle)
|
||||
|
||||
新增 `ErrorTypeThrottle` 错误类型,区分服务端限流(SSH MaxStartups 等)和真正的网络不可达。限流错误不计入连续失败计数,触发 500ms 退避后继续,避免误判目标不可达而提前放弃。
|
||||
|
||||
#### Web 版独立入口
|
||||
|
||||
- 拆分 `main.go` 为 `main_cli.go` 和 `main_web.go`
|
||||
- Web 版结果存储从内存替换为 SQLite 持久化(纯 Go 零 CGO)
|
||||
|
||||
---
|
||||
|
||||
### 🐛 Bug 修复
|
||||
|
||||
- **UDP 插件在 `-p` 指定端口时被跳过** — 用户指定 `-p 53,161` 等包含 UDP 端口时,DNS/SNMP 等 UDP 插件不会执行。现在按用户指定的端口过滤并正确调度
|
||||
- **Redis exploit 操作无超时保护** — exploit 阶段移除了全部 deadline,服务端卡滞时 goroutine 永久阻塞。现在设置 30s 操作超时
|
||||
- **Redis readReply 吞没非超时错误** — 只要读到任何数据就忽略所有错误,可能返回截断响应。现在仅对 timeout 类型错误做容忍
|
||||
- **service_probe 连接丢失后静默成功** — Write/Read 在 Conn=nil 时返回 nil 而非错误,导致后续探测静默跳过。现在返回明确的 errConnLost
|
||||
- **SNMP 探测成功但终端无输出** — SNMP 插件缺少 `session.LogVuln` 调用,成功结果只写入文件不在终端显示
|
||||
#### 插件调度(#586 #587 #588)
|
||||
|
||||
### ⚡ 可靠性改善
|
||||
- **非标准端口服务无法匹配插件** — SSH 在 8881 端口,端口匹配失败导致插件不执行。新增服务名称缓存 + 指纹驱动回退匹配 (#588)
|
||||
- **移除误导性的"无可用插件"日志** — 预检基于静态端口匹配,不代表实际不执行 (#588)
|
||||
- **默认扫描 POC 结果缺失** — `executeRules` 返回空 `vulName` 导致检测结果被丢弃 (#586)
|
||||
- **批量扫描(-hf)POC 缺失** — 并发压力下 HTTP 请求瞬时失败未重试,跳过指纹识别和 POC 触发。加入指数退避重试 (#587)
|
||||
- **UDP 插件在 `-p` 指定端口时被跳过** — 现在按用户指定端口过滤并正确调度
|
||||
- **`-full` 模式下 Web 插件跳过 `IsMarkedWebService` 检查**
|
||||
- **不确定服务补做 HTTP 回退探测**,覆盖自定义 HTTP 框架漏网场景
|
||||
|
||||
- **协议级超时下限(ModuleTimeout)** — 新增 `Config.ModuleTimeout()` 方法,保证插件级交互超时不低于 3s。自适应系统将端口扫描超时压到 1s 时,SSH 握手/SNMP 探测/数据库认证等多轮交互协议不再受影响。全部 44 个服务插件已迁移
|
||||
- **SSH 爆破并发优化** — SSH 并发从 30 降至 3,避免触发 OpenSSH MaxStartups 限流导致大量连接被丢弃
|
||||
- **SSH 限流错误分类(ErrorTypeThrottle)** — 新增限流错误类型,区分服务端限流(MaxStartups)和真正的网络不可达。限流错误不计入连续失败计数,仅触发 500ms 退避后继续,避免误判目标不可达而提前放弃
|
||||
- **SSH 握手 TCP deadline** — 在 SSH NewClientConn 前设置 TCP 级别 deadline 兜底整个握手过程,握手成功后清除
|
||||
- **进度条竞态修复** — `ProgressManager.isActive` 从 `bool` 改为 `atomic.Bool`,消除 UpdateProgress 与 FinishProgress 之间的数据竞态
|
||||
- **gmtls stdout 竞态修复** — 移除 `suppressGMTLSStdout` 中对 `os.Stdout` 的非同步重定向,消除与 gmtls 内部 goroutine 的数据竞态
|
||||
- **Lint 清理** — 修复 cassandra/ipmi/mongodb/webscan 中的 ineffassign、unused、errcheck 问题
|
||||
#### UDP 插件
|
||||
|
||||
### 📊 实测验证
|
||||
- **UDP 插件阻塞导致扫描无法结束** — `conn.Read()` 在目标不响应时无限阻塞。所有 UDP 插件(SNMP/BACnet/DNS/IPMI/TFTP)统一使用 context timeout + conn.Close 双保险
|
||||
- **SNMP community 爆破混入通用密码字典** — 57 个通用密码串行探测导致 10 分钟阻塞,精简为 8 个专用 community
|
||||
|
||||
| 指标 | v2.2.0-rc | v2.2.0-rc.1 |
|
||||
|------|-----------|-------------|
|
||||
| SSH `-m ssh` 爆破成功率 | ~60% | 100% (10/10) |
|
||||
| SNMP `-p 161` 终端输出 | ✗ 不显示 | ✓ 正常 |
|
||||
| UDP 插件 `-p` 指定端口 | ✗ 跳过 | ✓ 正确调度 |
|
||||
| Redis exploit 超时保护 | ✗ 无 | ✓ 30s |
|
||||
#### SSH
|
||||
|
||||
- **SSH goroutine 泄漏** — `ssh.NewClientConn` 不接受 context,context 取消后底层 TCP 连接未关闭,大规模扫描时泄漏数万 goroutine
|
||||
- **SSH 握手无 TCP deadline 兜底** — 在 `NewClientConn` 前设置 deadline,握手成功后清除
|
||||
- **SSH 爆破并发过高** — 从 30 降至 3,避免触发 OpenSSH MaxStartups 限流
|
||||
|
||||
#### Redis
|
||||
|
||||
- **Redis exploit 无超时保护** — exploit 阶段移除了全部 deadline,改为 30s 超时
|
||||
- **Redis readReply 吞没非超时错误** — 现在仅对 timeout 类型错误做容忍
|
||||
|
||||
#### POC 引擎
|
||||
|
||||
- **DetectPocFormat 误判含 transport 的 fscan POC 为 xray 格式** — 修复后 388 个 POC 全部正确加载(之前 8 个失败)
|
||||
- **CEL clustersend 结果判断错误** — 从字符串比较改为类型断言
|
||||
- **CEL wait() 函数 nil Reverse 指针 panic**
|
||||
- **reverseCheck 无超时** — 加 10s 超时防止 ceye API 阻塞
|
||||
- **正则编译结果未缓存** — `doSearch`/`bmatches` 缓存到 `sync.Map`
|
||||
|
||||
#### 参数与输出
|
||||
|
||||
- **`-gt` 全局超时参数是死代码** — 现在真正生效,超时后取消所有扫描任务
|
||||
- **`-nopoc` 禁用 POC 时仍输出错误日志** — 已修复
|
||||
- **`-debug` 日志文件写入失败** — `applyLogLevel` 重建 Logger 时丢失 `DebugLogFile` 配置
|
||||
- **`-hash` 不支持 LM:NT 格式** — 现在支持 `aad3b435b51404ee:31d6cfe0d16ae931...` 标准格式
|
||||
- **`-pwd` 不支持逗号分隔多个密码** — 现在 `-pwd "123,456,root"` 正确拆分
|
||||
- **`-nobr` 跳过了 Redis 未授权检测** — 未授权是服务探测不是爆破,不受 `-nobr` 影响
|
||||
- **非终端输出时 ANSI 控制码覆盖扫描结果** — 管道/重定向时自动禁用进度条和颜色
|
||||
- **静默模式 NDJSON banner 过长** — Redis INFO ~5KB 截断至 200 字符
|
||||
- **CSV 漏洞 Type 列为空** — 补全 `type` 字段
|
||||
- **SNMP 探测成功但终端无输出** — 补充 `session.LogVuln` 调用
|
||||
|
||||
#### 其他
|
||||
|
||||
- **service_probe 连接丢失后静默成功** — `Write`/`Read` 在 `Conn=nil` 时返回明确错误
|
||||
- **MongoDB readMongoMsg 未设置读超时**
|
||||
- **TXTWriter.Close Sync 失败后未关闭文件**
|
||||
- **MySQL 3306 服务名误识别为 genetec-5400** — nmap 指纹库误匹配,通过 banner 特征校正
|
||||
- **gmtls stdout 竞态** — 移除 `os.Stdout` 非同步重定向
|
||||
|
||||
---
|
||||
|
||||
### 🏗️ 架构优化
|
||||
|
||||
- **统一服务缓存** — `webServiceCache` 扩展为通用 `serviceCache`,下沉到 per-session State,消除多实例缓存串台
|
||||
- **CEL 表达式编译缓存** — 同一 POC 的所有规则共享编译后的 Program
|
||||
- **POC 全局状态消除** — `allPocs` 全局变量改为 `pocStore` 按 PocPath 缓存,并发场景不再互相覆盖
|
||||
- **进度条竞态修复** — `isActive` 改为 `atomic.Bool`
|
||||
- **Lint 全量修复** — cassandra/ipmi/mongodb/webscan 的 ineffassign、unused、errcheck
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ bin/
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
/fscan_cli
|
||||
/fscan_web
|
||||
/embed-agent
|
||||
|
||||
# Web UI build / Web前端构建
|
||||
web-ui/node_modules/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
内网综合扫描工具,一键自动化漏扫。
|
||||
|
||||
**版本**: 2.2.0-rc
|
||||
**版本**: 2.2.0-rc.1
|
||||
|
||||
## 功能特性
|
||||
|
||||
@@ -186,10 +186,10 @@
|
||||
|
||||
```bash
|
||||
# 标准编译
|
||||
go build -ldflags="-s -w" -trimpath -o fscan main.go
|
||||
go build -ldflags="-s -w" -trimpath -o fscan .
|
||||
|
||||
# 带Web管理界面
|
||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan main.go
|
||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan-web .
|
||||
```
|
||||
|
||||
## 安装
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
Comprehensive intranet scanning tool for automated vulnerability assessment.
|
||||
|
||||
**Version**: 2.2.0-rc
|
||||
**Version**: 2.2.0-rc.1.1
|
||||
|
||||
## Features
|
||||
|
||||
@@ -185,10 +185,10 @@ Comprehensive intranet scanning tool for automated vulnerability assessment.
|
||||
|
||||
```bash
|
||||
# Standard build
|
||||
go build -ldflags="-s -w" -trimpath -o fscan main.go
|
||||
go build -ldflags="-s -w" -trimpath -o fscan .
|
||||
|
||||
# With Web UI
|
||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan main.go
|
||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan-web .
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
@@ -137,6 +137,7 @@ func Flag(Info *HostInfo) error {
|
||||
flag.StringVar(&fv.TargetURL, "u", "", i18n.GetText("flag_target_url"))
|
||||
flag.StringVar(&fv.URLsFile, "uf", "", i18n.GetText("flag_urls_file"))
|
||||
flag.StringVar(&fv.Cookie, "cookie", "", i18n.GetText("flag_cookie"))
|
||||
flag.StringVar(&fv.UserAgent, "ua", "", i18n.GetText("flag_user_agent"))
|
||||
flag.Int64Var(&fv.WebTimeout, "wt", 5, i18n.GetText("flag_web_timeout"))
|
||||
flag.IntVar(&fv.MaxRedirects, "max-redirect", 10, i18n.GetText("flag_max_redirects"))
|
||||
flag.StringVar(&fv.HTTPProxy, "proxy", "", i18n.GetText("flag_http_proxy"))
|
||||
|
||||
@@ -27,7 +27,6 @@ type FlagVars struct {
|
||||
ExcludeHostsFile string
|
||||
Ports string
|
||||
ExcludePorts string
|
||||
AddPorts string
|
||||
HostsFile string
|
||||
PortsFile string
|
||||
|
||||
@@ -226,7 +225,7 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
|
||||
},
|
||||
HTTP: HTTPConfig{
|
||||
Cookie: fv.Cookie,
|
||||
UserAgent: fv.UserAgent,
|
||||
UserAgent: defaultUserAgent(fv.UserAgent),
|
||||
Accept: fv.Accept,
|
||||
},
|
||||
LocalExploit: LocalExploitConfig{
|
||||
@@ -246,3 +245,11 @@ func BuildConfigFromFlags(fv *FlagVars) *Config {
|
||||
func isStdoutTerminal() bool {
|
||||
return term.IsTerminal(int(os.Stdout.Fd()))
|
||||
}
|
||||
|
||||
// defaultUserAgent 用户未通过 -ua 指定时回退到默认 UA,避免发送空 User-Agent 被 WAF 识别
|
||||
func defaultUserAgent(ua string) string {
|
||||
if ua != "" {
|
||||
return ua
|
||||
}
|
||||
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
}
|
||||
|
||||
+3
-2
@@ -955,8 +955,9 @@ func TestBuildConfigFromFlags_BoundaryValues(t *testing.T) {
|
||||
if cfg.HTTP.Cookie != "" {
|
||||
t.Errorf("Cookie 应该为空")
|
||||
}
|
||||
if cfg.HTTP.UserAgent != "" {
|
||||
t.Errorf("UserAgent 应该为空")
|
||||
// 空输入回退到默认 UA,避免发送空 User-Agent
|
||||
if cfg.HTTP.UserAgent == "" {
|
||||
t.Errorf("UserAgent 空输入应回退到默认 UA")
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -64,6 +64,8 @@ flag_urls_file:
|
||||
other: "URLs file"
|
||||
flag_cookie:
|
||||
other: "HTTP Cookie"
|
||||
flag_user_agent:
|
||||
other: "Custom User-Agent header"
|
||||
flag_web_timeout:
|
||||
other: "Web timeout"
|
||||
flag_max_redirects:
|
||||
|
||||
@@ -64,6 +64,8 @@ flag_urls_file:
|
||||
other: "URL文件"
|
||||
flag_cookie:
|
||||
other: "HTTP Cookie"
|
||||
flag_user_agent:
|
||||
other: "自定义 User-Agent 请求头"
|
||||
flag_web_timeout:
|
||||
other: "Web超时时间"
|
||||
flag_max_redirects:
|
||||
|
||||
@@ -203,27 +203,9 @@ func (b *BaseScanStrategy) isPluginPassesFilterType(pluginName string, isCustomM
|
||||
}
|
||||
}
|
||||
|
||||
// LogPluginInfo 输出插件信息
|
||||
// LogPluginInfo 默认不输出插件信息(service 默认端口模式有意保持安静,减少干扰)。
|
||||
// 子类 LocalScanStrategy / ServiceScanStrategy 按需重写。
|
||||
func (b *BaseScanStrategy) LogPluginInfo(config *common.Config, session *common.ScanSession) {
|
||||
allPlugins, isCustomMode := b.GetPlugins(config)
|
||||
|
||||
var prefix string
|
||||
switch b.filterType {
|
||||
case FilterLocal:
|
||||
prefix = i18n.GetText("concurrency_local_plugin")
|
||||
case FilterService:
|
||||
prefix = i18n.GetText("concurrency_service_plugin")
|
||||
case FilterWeb:
|
||||
prefix = i18n.GetText("concurrency_web_plugin")
|
||||
default:
|
||||
prefix = i18n.GetText("concurrency_plugin")
|
||||
}
|
||||
|
||||
// 插件信息不再输出,减少干扰
|
||||
_ = allPlugins
|
||||
_ = isCustomMode
|
||||
_ = prefix
|
||||
_ = session
|
||||
}
|
||||
|
||||
// formatPluginList 格式化插件列表(超过5个时精简显示)
|
||||
|
||||
+29
-13
@@ -56,19 +56,21 @@ func DetectHTTPSchemeContext(ctx context.Context, host string, port int, config
|
||||
return "https"
|
||||
}
|
||||
|
||||
// 第二步:尝试国密TLS握手(GM TLS fallback)
|
||||
gmConn, gmErr := gmtls.DialWithDialer(
|
||||
tlsDialer,
|
||||
"tcp", addr,
|
||||
&gmtls.Config{
|
||||
GMSupport: gmtls.NewGMSupport(),
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
)
|
||||
|
||||
if gmErr == nil {
|
||||
_ = gmConn.Close()
|
||||
return "https-gm"
|
||||
// 第二步:仅在标准 TLS 握手级别失败(cipher/protocol 不兼容)时尝试国密
|
||||
// 连接级别失败(timeout/refused/非 TLS 端口)不需要尝试
|
||||
if maybeGMTLS(err) {
|
||||
gmConn, gmErr := gmtls.DialWithDialer(
|
||||
tlsDialer,
|
||||
"tcp", addr,
|
||||
&gmtls.Config{
|
||||
GMSupport: gmtls.NewGMSupport(),
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
)
|
||||
if gmErr == nil {
|
||||
_ = gmConn.Close()
|
||||
return "https-gm"
|
||||
}
|
||||
}
|
||||
|
||||
// TLS和GM TLS都失败,尝试HTTP
|
||||
@@ -491,6 +493,20 @@ func (s *WebScanStrategy) createTargetFromURLWithSession(baseInfo common.HostInf
|
||||
return &urlInfo
|
||||
}
|
||||
|
||||
// maybeGMTLS 判断标准 TLS 握手错误是否可能是国密服务端
|
||||
// 只有 cipher/protocol 层面的不兼容才值得尝试国密回退
|
||||
// 连接超时、拒绝、非 TLS 端口等连接级错误直接跳过
|
||||
func maybeGMTLS(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
s := err.Error()
|
||||
return strings.Contains(s, "handshake failure") ||
|
||||
strings.Contains(s, "protocol version") ||
|
||||
strings.Contains(s, "no mutual") ||
|
||||
strings.Contains(s, "cipher suite")
|
||||
}
|
||||
|
||||
func hasMalformedURLPort(host string) bool {
|
||||
if strings.HasPrefix(host, "[") {
|
||||
end := strings.LastIndexByte(host, ']')
|
||||
|
||||
@@ -198,6 +198,10 @@ func smb1GetResponse(conn net.Conn) ([]byte, *smbHeader, error) {
|
||||
sizeBuf := make([]byte, 4)
|
||||
copy(sizeBuf[1:], buf[1:])
|
||||
size := int(binary.BigEndian.Uint32(sizeBuf))
|
||||
// 畸形响应(size < SMB 头长度)会导致后续 buf[:smbHeaderSize] 越界 panic
|
||||
if size < smbHeaderSize {
|
||||
return nil, nil, fmt.Errorf("SMB1 response too short: %d bytes", size)
|
||||
}
|
||||
// SMB
|
||||
buf = make([]byte, size)
|
||||
_, err = io.ReadFull(conn, buf)
|
||||
|
||||
@@ -325,7 +325,7 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n
|
||||
return
|
||||
}
|
||||
|
||||
_ = conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
_ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout()))
|
||||
|
||||
dbfilename, dir, err := p.getConfig(conn)
|
||||
if err != nil {
|
||||
|
||||
@@ -118,7 +118,7 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
|
||||
}
|
||||
|
||||
// 建立TCP连接
|
||||
conn, err := session.DialTCP(ctx, "tcp", target, config.Timeout)
|
||||
conn, err := session.DialTCP(ctx, "tcp", target, moduleTimeout)
|
||||
if err != nil {
|
||||
return &AuthResult{
|
||||
Success: false,
|
||||
@@ -277,7 +277,7 @@ func (p *SSHPlugin) scanWithKey(ctx context.Context, info *common.HostInfo, sess
|
||||
func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
|
||||
target := info.Target()
|
||||
|
||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
|
||||
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
|
||||
if err != nil {
|
||||
return &ScanResult{
|
||||
Success: false,
|
||||
|
||||
+14
-2
@@ -508,8 +508,9 @@ func DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (*
|
||||
oResp, err = requestClient(false).Do(req)
|
||||
}
|
||||
|
||||
// 标准TLS连接失败时,尝试国密TLS客户端
|
||||
if err != nil && req.URL.Scheme == "https" {
|
||||
// 标准TLS握手级别失败时,尝试国密TLS客户端
|
||||
// 跳过连接超时、拒绝等非 TLS 相关错误,避免无意义的国密握手尝试
|
||||
if err != nil && req.URL.Scheme == "https" && maybeGMTLSError(err) {
|
||||
if req.GetBody != nil {
|
||||
if body, bodyErr := req.GetBody(); bodyErr == nil {
|
||||
req.Body = body
|
||||
@@ -682,3 +683,14 @@ func getRespBody(oResp *http.Response) ([]byte, error) {
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func maybeGMTLSError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
s := err.Error()
|
||||
return strings.Contains(s, "handshake failure") ||
|
||||
strings.Contains(s, "protocol version") ||
|
||||
strings.Contains(s, "no mutual") ||
|
||||
strings.Contains(s, "cipher suite")
|
||||
}
|
||||
|
||||
@@ -1238,7 +1238,7 @@ func TestDoRequestSkipsNilGMTLSFallback(t *testing.T) {
|
||||
}()
|
||||
|
||||
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
||||
return nil, errors.New("standard tls failed")
|
||||
return nil, errors.New("tls: handshake failure")
|
||||
})}
|
||||
ClientNoRedirectGM = nil
|
||||
|
||||
@@ -1261,7 +1261,7 @@ func TestDoRequestReplaysBodyForGMTLSFallback(t *testing.T) {
|
||||
|
||||
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
_, _ = io.ReadAll(req.Body)
|
||||
return nil, errors.New("standard tls failed")
|
||||
return nil, errors.New("tls: handshake failure")
|
||||
})}
|
||||
|
||||
var gotBody string
|
||||
|
||||
Reference in New Issue
Block a user