mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-11-01 18:51:45 +08:00
fix:more port/host
#IH4IIN
This commit is contained in:
+9
-5
@@ -530,23 +530,27 @@ func (m *wafSystenService) run() {
|
||||
}
|
||||
//如果本次是关闭,那么应该关闭主机
|
||||
if hosts[0].START_STATUS == 1 {
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hosts[0])
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hostsOld)
|
||||
}
|
||||
//如果本次ssl和上次ssl不同
|
||||
if hosts[0].Ssl != hostsOld.Ssl || hosts[0].Keyfile != hostsOld.Keyfile || hosts[0].Certfile != hostsOld.Certfile {
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hosts[0])
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hostsOld)
|
||||
}
|
||||
//绑定更多域名变更了
|
||||
if hosts[0].BindMoreHost != hostsOld.BindMoreHost {
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hosts[0])
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hostsOld)
|
||||
}
|
||||
//绑定更多端口变更了(副端口增删),需先移除旧配置再重载
|
||||
if hosts[0].BindMorePort != hostsOld.BindMorePort {
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hostsOld)
|
||||
}
|
||||
// 远程 IP 或端口变化,需重载
|
||||
if hosts[0].Remote_ip != hostsOld.Remote_ip || hosts[0].Remote_port != hostsOld.Remote_port {
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hosts[0])
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hostsOld)
|
||||
}
|
||||
// 负载状态或策略变化,需重载
|
||||
if hosts[0].IsEnableLoadBalance != hostsOld.IsEnableLoadBalance || hosts[0].LoadBalanceStage != hostsOld.LoadBalanceStage {
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hosts[0])
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.RemoveHost(hostsOld)
|
||||
}
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.LoadHost(hosts[0])
|
||||
globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.StartAllProxyServer()
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"SamWaf/utils"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -307,8 +308,26 @@ func (receiver *WafHostService) CheckPortExistApi(port int) int64 {
|
||||
|
||||
func (receiver *WafHostService) CheckAvailablePortExistApi(port int) int64 {
|
||||
var total int64 = 0
|
||||
global.GWAF_LOCAL_DB.Model(&model.Hosts{}).Where(" start_status = 0 and port=?", port).Count(&total)
|
||||
return total
|
||||
// 先检查主端口
|
||||
global.GWAF_LOCAL_DB.Model(&model.Hosts{}).Where("start_status = 0 and port=?", port).Count(&total)
|
||||
if total > 0 {
|
||||
return total
|
||||
}
|
||||
// 再检查 BindMorePort(逗号分隔的副端口列表)
|
||||
// 取出所有启用且有副端口配置的主机,在 Go 层解析,避免 DB 字符串模糊匹配误判
|
||||
var hosts []model.Hosts
|
||||
global.GWAF_LOCAL_DB.Model(&model.Hosts{}).
|
||||
Where("start_status = 0 and bind_more_port != ''").
|
||||
Select("bind_more_port").Find(&hosts)
|
||||
portStr := strconv.Itoa(port)
|
||||
for _, h := range hosts {
|
||||
for _, p := range strings.Split(h.BindMorePort, ",") {
|
||||
if strings.TrimSpace(p) == portStr {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (receiver *WafHostService) IsEmptyHost() bool {
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package waf_service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// checkBindMorePortContains 模拟修复后 CheckAvailablePortExistApi 中
|
||||
// "扫描 BindMorePort 字段" 的核心逻辑,可脱离 DB 独立测试
|
||||
func checkBindMorePortContains(bindMorePorts []string, targetPort int) bool {
|
||||
portStr := strconv.Itoa(targetPort)
|
||||
for _, bindMorePort := range bindMorePorts {
|
||||
if bindMorePort == "" {
|
||||
continue
|
||||
}
|
||||
for _, p := range strings.Split(bindMorePort, ",") {
|
||||
if strings.TrimSpace(p) == portStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestCheckBindMorePort_ContainsPort
|
||||
// Fix3 核心逻辑:给定一组主机的 BindMorePort 字段值,判断目标端口是否被使用
|
||||
func TestCheckBindMorePort_ContainsPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bindMorePorts []string // 模拟多个主机的 bind_more_port 字段
|
||||
targetPort int
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "端口在副端口列表中(单个主机)",
|
||||
bindMorePorts: []string{"8080,9090"},
|
||||
targetPort: 8080,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "端口在副端口列表末尾",
|
||||
bindMorePorts: []string{"8080,9090"},
|
||||
targetPort: 9090,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "端口不在任何副端口列表中",
|
||||
bindMorePorts: []string{"8080,9090", "7070,7071"},
|
||||
targetPort: 6060,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "端口在第二个主机的副端口中",
|
||||
bindMorePorts: []string{"8080", "9090,6060"},
|
||||
targetPort: 6060,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "副端口列表为空",
|
||||
bindMorePorts: []string{"", ""},
|
||||
targetPort: 8080,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "副端口有多余空格",
|
||||
bindMorePorts: []string{" 8080 , 9090 "},
|
||||
targetPort: 8080,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "单个副端口精确匹配",
|
||||
bindMorePorts: []string{"8080"},
|
||||
targetPort: 8080,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "避免数字前缀误匹配:808 不应匹配 8080",
|
||||
bindMorePorts: []string{"8080,9090"},
|
||||
targetPort: 808,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "避免数字后缀误匹配:80800 不应匹配 8080",
|
||||
bindMorePorts: []string{"8080,9090"},
|
||||
targetPort: 80800,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := checkBindMorePortContains(tc.bindMorePorts, tc.targetPort)
|
||||
if got != tc.want {
|
||||
t.Errorf("checkBindMorePortContains(%v, %d) = %v,期望 %v",
|
||||
tc.bindMorePorts, tc.targetPort, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckAvailablePortExistApi_BindMorePortScenario
|
||||
// 文档化测试:描述 Fix3 修复前后的行为差异
|
||||
//
|
||||
// 修复前:CheckAvailablePortExistApi 只查 WHERE port=X
|
||||
// - 站点 A: Port=80, BindMorePort="8080"
|
||||
// - CheckAvailablePortExistApi(8080) → 返回 0(误判为无人使用)
|
||||
// - 结果:RemovePortServer 会把 8080 端口的 HTTP server 关掉
|
||||
//
|
||||
// 修复后:同时扫描 bind_more_port 字段
|
||||
// - CheckAvailablePortExistApi(8080) → 返回 1(正确识别为在用)
|
||||
// - 结果:RemovePortServer 不会关闭 8080 端口
|
||||
func TestCheckAvailablePortExistApi_BindMorePortScenario(t *testing.T) {
|
||||
// 模拟:主机 A Port=80 BindMorePort="8080",没有主机以 8080 为主端口
|
||||
mainPortHosts := []int{80, 443, 9090} // 所有主机的主端口
|
||||
bindMorePortList := []string{"8080", "7070,7071"} // 所有主机的 BindMorePort 字段
|
||||
|
||||
targetPort := 8080
|
||||
|
||||
// 旧逻辑:只查主端口
|
||||
foundByMainPort := false
|
||||
for _, p := range mainPortHosts {
|
||||
if p == targetPort {
|
||||
foundByMainPort = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 新逻辑:主端口 + BindMorePort
|
||||
foundByBindMore := checkBindMorePortContains(bindMorePortList, targetPort)
|
||||
foundNew := foundByMainPort || foundByBindMore
|
||||
|
||||
if foundByMainPort {
|
||||
t.Errorf("测试数据有误:不应该有主机以 %d 为主端口", targetPort)
|
||||
}
|
||||
if !foundByBindMore {
|
||||
t.Error("Fix3:新逻辑应能在 BindMorePort 中找到端口 8080")
|
||||
}
|
||||
if !foundNew {
|
||||
t.Error("Fix3:修复后的逻辑应返回端口 8080 仍在使用中")
|
||||
}
|
||||
|
||||
// 明确表达:旧逻辑会错误地关掉端口,新逻辑不会
|
||||
t.Logf("旧逻辑 foundByMainPort=%v(会错误关掉端口)", foundByMainPort)
|
||||
t.Logf("新逻辑 foundNew=%v(正确保留端口)", foundNew)
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func (ac *AllCertificate) RemoveSSL(domain string) error {
|
||||
domain = strings.ToLower(domain)
|
||||
_, ok := ac.Map[domain]
|
||||
if ok {
|
||||
ac.Map[domain] = nil
|
||||
delete(ac.Map, domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ func (waf *WafEngine) LoadHost(inHost model.Hosts) []innerbean.ServerRunTime {
|
||||
_, ok := waf.ServerOnline.Get(port)
|
||||
if ok == false {
|
||||
if inHost.START_STATUS == 0 {
|
||||
if port == 443 {
|
||||
if port == 443 || (inHost.Ssl == 1 && port != 80) {
|
||||
waf.ServerOnline.Set(port, innerbean.ServerRunTime{
|
||||
ServerType: "https",
|
||||
Port: port,
|
||||
@@ -261,8 +261,7 @@ func (waf *WafEngine) LoadHost(inHost model.Hosts) []innerbean.ServerRunTime {
|
||||
for _, port := range ports {
|
||||
//目标关系情况
|
||||
waf.HostTarget[inHost.Host+":"+strconv.Itoa(port)] = hostsafe
|
||||
//赋值到对照表里面
|
||||
waf.HostCode[inHost.Code] = inHost.Host + ":" + strconv.Itoa(port)
|
||||
// 注意:HostCode[code] 始终指向主端口 key(已在上方赋值),不在此处覆盖
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +285,16 @@ func (waf *WafEngine) LoadHost(inHost model.Hosts) []innerbean.ServerRunTime {
|
||||
if inHost.BindMoreHost != "" {
|
||||
lines := strings.Split(inHost.BindMoreHost, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// 主端口
|
||||
waf.HostTargetMoreDomain[line+":"+strconv.Itoa(inHost.Port)] = inHost.Code
|
||||
// 副端口(BindMorePort)也需要注册,否则副域名+副端口无法路由
|
||||
for _, extraPort := range ports {
|
||||
waf.HostTargetMoreDomain[line+":"+strconv.Itoa(extraPort)] = inHost.Code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,8 +349,20 @@ func (waf *WafEngine) RemoveHost(host model.Hosts) {
|
||||
// 移除当前信息
|
||||
//a.移除对照关系
|
||||
delete(waf.HostCode, host.Code)
|
||||
//b.移除主机保护信息
|
||||
//b.移除主机保护信息(主端口)
|
||||
delete(waf.HostTarget, host.Host+":"+strconv.Itoa(host.Port))
|
||||
//b2.移除 BindMorePort 副端口对应的 HostTarget 条目,避免残留 stale 指针
|
||||
if host.BindMorePort != "" && host.GLOBAL_HOST == 0 {
|
||||
for _, portStr := range strings.Split(host.BindMorePort, ",") {
|
||||
if p, err := strconv.Atoi(strings.TrimSpace(portStr)); err == nil {
|
||||
delete(waf.HostTarget, host.Host+":"+strconv.Itoa(p))
|
||||
}
|
||||
}
|
||||
}
|
||||
//b3.移除 AutoJumpHTTPS 添加的 80 端口条目
|
||||
if host.AutoJumpHTTPS == 1 {
|
||||
delete(waf.HostTarget, host.Host+":80")
|
||||
}
|
||||
//c.移除某个端口下的证书数据
|
||||
waf.AllCertificate.RemoveSSL(host.Host)
|
||||
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
package wafenginecore
|
||||
|
||||
import (
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/wafenginmodel"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// newTestWafEngine 创建不依赖 DB 的最小 WafEngine,供单元测试使用
|
||||
func newTestWafEngine() *WafEngine {
|
||||
return &WafEngine{
|
||||
HostTarget: make(map[string]*wafenginmodel.HostSafe),
|
||||
HostCode: make(map[string]string),
|
||||
HostTargetNoPort: make(map[string]string),
|
||||
HostTargetMoreDomain: make(map[string]string),
|
||||
ServerOnline: wafenginmodel.NewSafeServerMap(),
|
||||
TransportPool: make(map[string]*http.Transport),
|
||||
AllCertificate: AllCertificate{Map: make(map[string]*tls.Certificate)},
|
||||
}
|
||||
}
|
||||
|
||||
// simulateLoadHostMaps 模拟 LoadHost 中纯 map 赋值的部分(不含 DB 查询)
|
||||
// 严格按照修复后的逻辑编写,用于在无 DB 环境下验证 Fix1/Fix2 正确性
|
||||
func simulateLoadHostMaps(waf *WafEngine, inHost model.Hosts) {
|
||||
hostsafe := &wafenginmodel.HostSafe{Host: inHost}
|
||||
|
||||
// 解析副端口列表(不写入 ServerOnline,避免 RemovePortServer 触发 DB 查询)
|
||||
var extraPorts []int
|
||||
if inHost.BindMorePort != "" && inHost.GLOBAL_HOST == 0 {
|
||||
for _, ps := range strings.Split(inHost.BindMorePort, ",") {
|
||||
if p, err := strconv.Atoi(strings.TrimSpace(ps)); err == nil {
|
||||
extraPorts = append(extraPorts, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主端口注册
|
||||
waf.HostTarget[inHost.Host+":"+strconv.Itoa(inHost.Port)] = hostsafe
|
||||
// Fix2: HostCode 只指向主端口,不在副端口循环内覆盖
|
||||
waf.HostCode[inHost.Code] = inHost.Host + ":" + strconv.Itoa(inHost.Port)
|
||||
|
||||
// 副端口注册(只注册 HostTarget,不改 HostCode)
|
||||
for _, port := range extraPorts {
|
||||
waf.HostTarget[inHost.Host+":"+strconv.Itoa(port)] = hostsafe
|
||||
}
|
||||
|
||||
// Fix1: BindMoreHost 对每个端口(主 + 副)都注册 HostTargetMoreDomain
|
||||
if inHost.BindMoreHost != "" {
|
||||
for _, line := range strings.Split(inHost.BindMoreHost, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
waf.HostTargetMoreDomain[line+":"+strconv.Itoa(inHost.Port)] = inHost.Code
|
||||
for _, ep := range extraPorts {
|
||||
waf.HostTargetMoreDomain[line+":"+strconv.Itoa(ep)] = inHost.Code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Fix 1: 副域名 + 副端口 路由注册测试
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// TestHostTargetMoreDomain_AllPortCombinations
|
||||
// 验证:BindMoreHost × BindMorePort 的所有组合都被注册到 HostTargetMoreDomain
|
||||
func TestHostTargetMoreDomain_AllPortCombinations(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
inHost := model.Hosts{
|
||||
Code: "site001",
|
||||
Host: "main.example.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080,9090",
|
||||
BindMoreHost: "sub1.example.com\nsub2.example.com",
|
||||
}
|
||||
simulateLoadHostMaps(waf, inHost)
|
||||
|
||||
// 主域名 × 所有端口 应在 HostTarget
|
||||
for _, port := range []int{80, 8080, 9090} {
|
||||
key := "main.example.com:" + strconv.Itoa(port)
|
||||
if waf.HostTarget[key] == nil {
|
||||
t.Errorf("HostTarget 缺少条目 %s(主域名×端口组合未注册)", key)
|
||||
}
|
||||
}
|
||||
|
||||
// 副域名 × 所有端口 应在 HostTargetMoreDomain(Fix1 修复的场景)
|
||||
for _, domain := range []string{"sub1.example.com", "sub2.example.com"} {
|
||||
for _, port := range []int{80, 8080, 9090} {
|
||||
key := domain + ":" + strconv.Itoa(port)
|
||||
if waf.HostTargetMoreDomain[key] == "" {
|
||||
t.Errorf("HostTargetMoreDomain 缺少条目 %s(副域名×副端口未注册)", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostTargetMoreDomain_SinglePort
|
||||
// 只有主端口(无 BindMorePort),副域名仍应正确注册
|
||||
func TestHostTargetMoreDomain_SinglePort(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
inHost := model.Hosts{
|
||||
Code: "site002",
|
||||
Host: "main.example.com",
|
||||
Port: 443,
|
||||
BindMorePort: "",
|
||||
BindMoreHost: "www.example.com\nalias.example.com",
|
||||
}
|
||||
simulateLoadHostMaps(waf, inHost)
|
||||
|
||||
for _, domain := range []string{"www.example.com", "alias.example.com"} {
|
||||
key := domain + ":443"
|
||||
if waf.HostTargetMoreDomain[key] == "" {
|
||||
t.Errorf("HostTargetMoreDomain 缺少条目 %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostTargetMoreDomain_EmptyLines
|
||||
// BindMoreHost 包含空行时,不应注册空字符串键
|
||||
func TestHostTargetMoreDomain_EmptyLines(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
inHost := model.Hosts{
|
||||
Code: "site003",
|
||||
Host: "main.example.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080",
|
||||
BindMoreHost: "sub.example.com\n\n \n",
|
||||
}
|
||||
simulateLoadHostMaps(waf, inHost)
|
||||
|
||||
// 空行不应产生空键
|
||||
for key := range waf.HostTargetMoreDomain {
|
||||
if strings.HasPrefix(key, ":") || strings.Contains(key, " :") {
|
||||
t.Errorf("HostTargetMoreDomain 包含由空行产生的错误键 %q", key)
|
||||
}
|
||||
}
|
||||
|
||||
// 有效域名应正常注册
|
||||
for _, port := range []int{80, 8080} {
|
||||
key := "sub.example.com:" + strconv.Itoa(port)
|
||||
if waf.HostTargetMoreDomain[key] == "" {
|
||||
t.Errorf("HostTargetMoreDomain 缺少有效条目 %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Fix 2: HostCode 不被副端口覆盖
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// TestHostCode_AlwaysPointsToMainPort
|
||||
// 验证:有多个副端口时,HostCode[code] 始终指向主端口 key
|
||||
func TestHostCode_AlwaysPointsToMainPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
host string
|
||||
mainPort int
|
||||
bindMorePort string
|
||||
}{
|
||||
{"两个副端口", "a.example.com", 80, "8080,8888"},
|
||||
{"三个副端口", "b.example.com", 443, "9090,9091,9092"},
|
||||
{"单个副端口", "c.example.com", 80, "8080"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
inHost := model.Hosts{
|
||||
Code: "code_" + tc.host,
|
||||
Host: tc.host,
|
||||
Port: tc.mainPort,
|
||||
BindMorePort: tc.bindMorePort,
|
||||
}
|
||||
simulateLoadHostMaps(waf, inHost)
|
||||
|
||||
want := tc.host + ":" + strconv.Itoa(tc.mainPort)
|
||||
got := waf.HostCode["code_"+tc.host]
|
||||
if got != want {
|
||||
t.Errorf("HostCode[%s] = %q,期望 %q(副端口不应覆盖主端口)",
|
||||
"code_"+tc.host, got, want)
|
||||
}
|
||||
|
||||
// 验证 HostTarget 通过 HostCode 能正确找回 hostsafe
|
||||
if waf.HostTarget[got] == nil {
|
||||
t.Errorf("HostTarget[%s] 为 nil,HostCode 指向了无效 key", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostCode_SinglePort
|
||||
// 无副端口时,HostCode 应指向唯一端口
|
||||
func TestHostCode_SinglePort(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
inHost := model.Hosts{
|
||||
Code: "site_single",
|
||||
Host: "single.example.com",
|
||||
Port: 443,
|
||||
BindMorePort: "",
|
||||
}
|
||||
simulateLoadHostMaps(waf, inHost)
|
||||
|
||||
want := "single.example.com:443"
|
||||
got := waf.HostCode["site_single"]
|
||||
if got != want {
|
||||
t.Errorf("HostCode[site_single] = %q,期望 %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Fix 4: RemoveHost 清理所有副端口 HostTarget 条目
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// TestRemoveHost_CleansAllBindMorePortEntries
|
||||
// 验证:RemoveHost 后,主端口和所有副端口的 HostTarget 条目全部清除
|
||||
func TestRemoveHost_CleansAllBindMorePortEntries(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
host := model.Hosts{
|
||||
Code: "site_clean",
|
||||
Host: "clean.example.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080,9090",
|
||||
GLOBAL_HOST: 0,
|
||||
}
|
||||
simulateLoadHostMaps(waf, host)
|
||||
|
||||
// 删除前确认所有端口已注册
|
||||
for _, port := range []int{80, 8080, 9090} {
|
||||
key := "clean.example.com:" + strconv.Itoa(port)
|
||||
if waf.HostTarget[key] == nil {
|
||||
t.Fatalf("测试前提不满足:HostTarget 缺少 %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
waf.RemoveHost(host)
|
||||
|
||||
// 删除后所有端口条目应消失
|
||||
for _, port := range []int{80, 8080, 9090} {
|
||||
key := "clean.example.com:" + strconv.Itoa(port)
|
||||
if waf.HostTarget[key] != nil {
|
||||
t.Errorf("RemoveHost 后 HostTarget 仍有残留条目 %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
// HostCode 也应消失
|
||||
if waf.HostCode["site_clean"] != "" {
|
||||
t.Errorf("RemoveHost 后 HostCode[site_clean] 仍有值 %q", waf.HostCode["site_clean"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveHost_CleansMoreDomainEntries
|
||||
// 验证:RemoveHost 后,HostTargetMoreDomain 中该站点的所有条目被清除
|
||||
func TestRemoveHost_CleansMoreDomainEntries(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
host := model.Hosts{
|
||||
Code: "site_more",
|
||||
Host: "main.example.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080",
|
||||
BindMoreHost: "sub1.example.com\nsub2.example.com",
|
||||
GLOBAL_HOST: 0,
|
||||
}
|
||||
simulateLoadHostMaps(waf, host)
|
||||
|
||||
// 删除前确认注册了所有组合
|
||||
for _, domain := range []string{"sub1.example.com", "sub2.example.com"} {
|
||||
for _, port := range []int{80, 8080} {
|
||||
key := domain + ":" + strconv.Itoa(port)
|
||||
if waf.HostTargetMoreDomain[key] == "" {
|
||||
t.Fatalf("测试前提不满足:HostTargetMoreDomain 缺少 %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waf.RemoveHost(host)
|
||||
|
||||
// 删除后不应有任何该站点的 MoreDomain 条目
|
||||
for moreDomain, code := range waf.HostTargetMoreDomain {
|
||||
if code == "site_more" {
|
||||
t.Errorf("RemoveHost 后 HostTargetMoreDomain 中仍有 %s -> site_more", moreDomain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRemoveHost_AutoJumpHTTPS
|
||||
// 验证:AutoJumpHTTPS 站点删除后,额外注册的 :80 条目也被清除
|
||||
func TestRemoveHost_AutoJumpHTTPS(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
hostsafe := &wafenginmodel.HostSafe{Host: model.Hosts{Code: "site_ssl"}}
|
||||
waf.HostTarget["ssl.example.com:443"] = hostsafe
|
||||
waf.HostTarget["ssl.example.com:80"] = hostsafe // AutoJumpHTTPS 注册的
|
||||
waf.HostCode["site_ssl"] = "ssl.example.com:443"
|
||||
|
||||
host := model.Hosts{
|
||||
Code: "site_ssl",
|
||||
Host: "ssl.example.com",
|
||||
Port: 443,
|
||||
AutoJumpHTTPS: 1,
|
||||
}
|
||||
waf.RemoveHost(host)
|
||||
|
||||
if waf.HostTarget["ssl.example.com:443"] != nil {
|
||||
t.Error("RemoveHost 后 ssl.example.com:443 应被清除")
|
||||
}
|
||||
if waf.HostTarget["ssl.example.com:80"] != nil {
|
||||
t.Error("RemoveHost 后 AutoJumpHTTPS 的 :80 条目未清除")
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 综合场景:模拟 Issue 1 & Issue 2 完整生命周期
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// TestIssue1_SubDomainExtraPortRouting
|
||||
// Issue 1 场景:副域名+副端口 应能路由到正确的 hostsafe
|
||||
func TestIssue1_SubDomainExtraPortRouting(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
host := model.Hosts{
|
||||
Code: "A",
|
||||
Host: "primary.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080",
|
||||
BindMoreHost: "secondary.com",
|
||||
}
|
||||
simulateLoadHostMaps(waf, host)
|
||||
|
||||
cases := []struct {
|
||||
requestHost string
|
||||
wantCode string
|
||||
desc string
|
||||
}{
|
||||
{"primary.com:80", "A", "主域名+主端口"},
|
||||
{"primary.com:8080", "A", "主域名+副端口"},
|
||||
{"secondary.com:80", "A", "副域名+主端口"},
|
||||
{"secondary.com:8080", "A", "副域名+副端口(Fix1 的目标场景)"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
// 模拟 ServeHTTP 路由查找顺序
|
||||
gotCode := ""
|
||||
if target, ok := waf.HostTarget[c.requestHost]; ok {
|
||||
gotCode = target.Host.Code
|
||||
} else if code, ok := waf.HostTargetMoreDomain[c.requestHost]; ok {
|
||||
gotCode = code
|
||||
}
|
||||
|
||||
if gotCode != c.wantCode {
|
||||
t.Errorf("请求 %s:期望路由到 %q,实际 %q", c.requestHost, c.wantCode, gotCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssue2_DeleteSiteNotAffectOtherSiteExtraPort
|
||||
// Issue 2 场景:删除任意站点 B 后,站点 A 的副端口路由信息应完整保留
|
||||
func TestIssue2_DeleteSiteNotAffectOtherSiteExtraPort(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
// 站点 A:主端口 80 + 副端口 8080
|
||||
hostA := model.Hosts{
|
||||
Code: "A",
|
||||
Host: "siteA.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080",
|
||||
}
|
||||
simulateLoadHostMaps(waf, hostA)
|
||||
|
||||
// 站点 B:独立站点,主端口 9090
|
||||
hostB := model.Hosts{
|
||||
Code: "B",
|
||||
Host: "siteB.com",
|
||||
Port: 9090,
|
||||
}
|
||||
simulateLoadHostMaps(waf, hostB)
|
||||
|
||||
// 删除站点 B
|
||||
waf.RemoveHost(hostB)
|
||||
|
||||
// 站点 A 的主端口和副端口应仍然存在
|
||||
if waf.HostTarget["siteA.com:80"] == nil {
|
||||
t.Error("删除站点 B 后,站点 A 主端口 :80 不应受影响")
|
||||
}
|
||||
if waf.HostTarget["siteA.com:8080"] == nil {
|
||||
t.Error("删除站点 B 后,站点 A 副端口 :8080 不应受影响")
|
||||
}
|
||||
if waf.HostCode["A"] != "siteA.com:80" {
|
||||
t.Errorf("删除站点 B 后,站点 A 的 HostCode 应为 siteA.com:80,实际 %q", waf.HostCode["A"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestIssue2_CreateNewThenDeleteNotBreakExistingExtraPort
|
||||
// Issue 2 完整复现:新建防护再删除,不应影响已有站点的副端口
|
||||
func TestIssue2_CreateNewThenDeleteNotBreakExistingExtraPort(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
// 步骤1:建立站点 A,一个域名+多个端口
|
||||
hostA := model.Hosts{
|
||||
Code: "siteA",
|
||||
Host: "example.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080,8443",
|
||||
}
|
||||
simulateLoadHostMaps(waf, hostA)
|
||||
|
||||
// 步骤2:新建防护(站点 B)
|
||||
hostB := model.Hosts{
|
||||
Code: "siteB",
|
||||
Host: "newsite.com",
|
||||
Port: 7070,
|
||||
}
|
||||
simulateLoadHostMaps(waf, hostB)
|
||||
|
||||
// 步骤3:删除站点 B
|
||||
waf.RemoveHost(hostB)
|
||||
|
||||
// 验证:站点 A 的所有端口映射仍然完整
|
||||
expectedKeys := []string{
|
||||
"example.com:80",
|
||||
"example.com:8080",
|
||||
"example.com:8443",
|
||||
}
|
||||
for _, key := range expectedKeys {
|
||||
if waf.HostTarget[key] == nil {
|
||||
t.Errorf("删除站点 B 后,站点 A 的 HostTarget[%s] 被错误清除", key)
|
||||
}
|
||||
}
|
||||
|
||||
// 站点 B 的条目应已清除
|
||||
if waf.HostTarget["newsite.com:7070"] != nil {
|
||||
t.Error("站点 B 的条目应被 RemoveHost 清除")
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Bug A & B 修复验证:情况2 BindMorePort 变更 & RemoveHost 用旧数据
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// TestBindMorePortChange_OldPortCleaned
|
||||
// Bug A:修改副端口时,旧副端口的 HostTarget 条目应被清除
|
||||
// Bug B:RemoveHost 必须用旧数据才能清到旧端口(传新数据清不掉旧的)
|
||||
//
|
||||
// 修复前行为:BindMorePort "8080,9090" → "8080,7070"
|
||||
//
|
||||
// RemoveHost(hosts[0]) 删的是 8080+7070(新),9090(旧)残留
|
||||
//
|
||||
// 修复后行为:RemoveHost(hostsOld) 删的是 8080+9090(旧),然后 LoadHost 加载新配置
|
||||
func TestBindMorePortChange_OldPortCleaned(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
|
||||
// 初始状态:主端口 80,副端口 8080,9090
|
||||
oldHost := model.Hosts{
|
||||
Code: "siteX",
|
||||
Host: "x.example.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080,9090",
|
||||
}
|
||||
simulateLoadHostMaps(waf, oldHost)
|
||||
|
||||
// 确认旧状态
|
||||
for _, port := range []int{80, 8080, 9090} {
|
||||
if waf.HostTarget["x.example.com:"+strconv.Itoa(port)] == nil {
|
||||
t.Fatalf("测试前提不满足:HostTarget 缺少 x.example.com:%d", port)
|
||||
}
|
||||
}
|
||||
|
||||
// 用户修改副端口:9090 → 7070(新副端口列表 8080,7070)
|
||||
newHost := model.Hosts{
|
||||
Code: "siteX",
|
||||
Host: "x.example.com",
|
||||
Port: 80,
|
||||
BindMorePort: "8080,7070",
|
||||
}
|
||||
|
||||
// 模拟 main.go 情况2 修复后的行为:
|
||||
// BindMorePort 不同 → RemoveHost(hostsOld),然后 LoadHost(hosts[0])
|
||||
if newHost.BindMorePort != oldHost.BindMorePort {
|
||||
waf.RemoveHost(oldHost) // Bug B 修复:传旧数据
|
||||
}
|
||||
simulateLoadHostMaps(waf, newHost)
|
||||
|
||||
// 新端口 7070 应存在
|
||||
if waf.HostTarget["x.example.com:7070"] == nil {
|
||||
t.Error("新副端口 7070 应已注册到 HostTarget")
|
||||
}
|
||||
// 保留的端口 8080 应存在
|
||||
if waf.HostTarget["x.example.com:8080"] == nil {
|
||||
t.Error("副端口 8080 应仍然注册")
|
||||
}
|
||||
// 旧端口 9090 应被清除(Bug A+B 修复后)
|
||||
if waf.HostTarget["x.example.com:9090"] != nil {
|
||||
t.Error("旧副端口 9090 应在 RemoveHost(hostsOld) 后被清除(Bug A+B 未修复时会残留)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBindMorePortChange_WithOldDataVsNewData
|
||||
// 直接对比:传旧数据 vs 传新数据 到 RemoveHost,验证结果差异
|
||||
// 这个测试精确复现了 Bug B 的问题所在
|
||||
func TestBindMorePortChange_WithOldDataVsNewData(t *testing.T) {
|
||||
// 场景:副端口从 "8080,9090" 变为 "8080,7070"
|
||||
|
||||
t.Run("传新数据_旧端口9090残留", func(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
oldHost := model.Hosts{Code: "s1", Host: "h.com", Port: 80, BindMorePort: "8080,9090"}
|
||||
simulateLoadHostMaps(waf, oldHost)
|
||||
|
||||
newHost := model.Hosts{Code: "s1", Host: "h.com", Port: 80, BindMorePort: "8080,7070"}
|
||||
waf.RemoveHost(newHost) // Bug B:传新数据,9090 不会被清除
|
||||
simulateLoadHostMaps(waf, newHost)
|
||||
|
||||
// 9090 应被清除,但传新数据时实际没清
|
||||
if waf.HostTarget["h.com:9090"] != nil {
|
||||
t.Log("符合预期(Bug B 存在时):传新数据,旧端口 9090 残留 —— 这正是 Bug B 的表现")
|
||||
} else {
|
||||
t.Log("旧端口 9090 被清除(不受 Bug B 影响的情况)")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("传旧数据_旧端口9090被正确清除", func(t *testing.T) {
|
||||
waf := newTestWafEngine()
|
||||
oldHost := model.Hosts{Code: "s1", Host: "h.com", Port: 80, BindMorePort: "8080,9090"}
|
||||
simulateLoadHostMaps(waf, oldHost)
|
||||
|
||||
newHost := model.Hosts{Code: "s1", Host: "h.com", Port: 80, BindMorePort: "8080,7070"}
|
||||
waf.RemoveHost(oldHost) // 修复后:传旧数据
|
||||
simulateLoadHostMaps(waf, newHost)
|
||||
|
||||
// 9090 必须被清除
|
||||
if waf.HostTarget["h.com:9090"] != nil {
|
||||
t.Error("传旧数据后旧端口 9090 仍残留,Fix 未生效")
|
||||
}
|
||||
// 7070 必须存在
|
||||
if waf.HostTarget["h.com:7070"] == nil {
|
||||
t.Error("新端口 7070 未注册")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user