diff --git a/api/entrance.go b/api/entrance.go index 395d2f4..7d46124 100644 --- a/api/entrance.go +++ b/api/entrance.go @@ -39,6 +39,7 @@ type APIGroup struct { WafAnalysisApi WafPrivateInfoApi WafCacheRuleApi + WafTunnelApi } var APIGroupAPP = new(APIGroup) @@ -94,4 +95,5 @@ var ( wafPrivateInfoService = waf_service.WafPrivateInfoServiceApp wafCacheRuleService = waf_service.WafCacheRuleServiceApp + wafTunnelService = waf_service.WafTunnelServiceApp ) diff --git a/api/waf_tunnel_api.go b/api/waf_tunnel_api.go new file mode 100644 index 0000000..35d3f67 --- /dev/null +++ b/api/waf_tunnel_api.go @@ -0,0 +1,260 @@ +package api + +import ( + "SamWaf/enums" + "SamWaf/global" + "SamWaf/globalobj" + "SamWaf/innerbean" + "SamWaf/model/common/response" + "SamWaf/model/request" + "SamWaf/model/spec" + "SamWaf/model/waftunnelmodel" + "SamWaf/utils" + "errors" + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "strconv" + "strings" +) + +type WafTunnelApi struct { +} + +func (w *WafTunnelApi) AddApi(c *gin.Context) { + var req request.WafTunnelAddReq + err := c.ShouldBindJSON(&req) + if err == nil { + + portStrArray := strings.Split(req.Port, ",") + protocol := strings.ToLower(req.Protocol) + + for _, portItem := range portStrArray { + portItem = strings.TrimSpace(portItem) + if portItem == "" { + continue + } + + port, err := strconv.Atoi(portItem) + if err != nil { + continue + } + + // 根据协议类型检测端口 + var isUsed bool + switch protocol { + case "tcp": + isUsed = utils.TCPPortCheck(port) // TCP端口检测 + case "udp": + isUsed = utils.UDPPortCheck(port) // UDP端口检测 + default: + response.FailWithMessage("协议不支持", c) + return + } + + if isUsed { + global.GQEQUE_MESSAGE_DB.Enqueue(innerbean.OpResultMessageInfo{ + BaseMessageInfo: innerbean.BaseMessageInfo{OperaType: "端口告警", Server: global.GWAF_CUSTOM_SERVER_NAME}, + Msg: fmt.Sprintf("%s端口 %d 已被占用", strings.ToUpper(protocol), port), + Success: "true", + }) + response.FailWithMessage(fmt.Sprintf("%s端口 %d 已被占用", strings.ToUpper(protocol), port), c) + return + } + } + cnt := wafTunnelService.CheckIsExistApi(req) + if cnt == 0 { + tunnel, err := wafTunnelService.AddApi(req) + + if err == nil { + //发送新增通知信息 + var chanInfo = spec.ChanCommon{ + Type: enums.ChanComTypeTunnel, + OpType: enums.OP_TYPE_NEW, + Content: wafTunnelService.GetDetailByIdApi(tunnel.Id), + } + global.GWAF_CHAN_COMMON_MSG <- chanInfo + response.OkWithMessage("添加成功", c) + } else { + response.FailWithMessage("添加失败", c) + } + return + } else { + response.FailWithMessage("当前记录已经存在", c) + return + } + + } else { + response.FailWithMessage("解析失败", c) + } +} + +func (w *WafTunnelApi) GetDetailApi(c *gin.Context) { + var req request.WafTunnelDetailReq + err := c.ShouldBind(&req) + if err == nil { + bean := wafTunnelService.GetDetailApi(req) + response.OkWithDetailed(bean, "获取成功", c) + } else { + response.FailWithMessage("解析失败", c) + } +} + +func (w *WafTunnelApi) GetListApi(c *gin.Context) { + var req request.WafTunnelSearchReq + err := c.ShouldBindJSON(&req) + if err == nil { + Tunnel, total, _ := wafTunnelService.GetListApi(req) + response.OkWithDetailed(response.PageResult{ + List: Tunnel, + Total: total, + PageIndex: req.PageIndex, + PageSize: req.PageSize, + }, "获取成功", c) + } else { + response.FailWithMessage("解析失败", c) + } +} + +func (w *WafTunnelApi) DelApi(c *gin.Context) { + var req request.WafTunnelDelReq + err := c.ShouldBind(&req) + if err == nil { + tunnel := wafTunnelService.GetDetailByIdApi(req.Id) + err = wafTunnelService.DelApi(req) + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + response.FailWithMessage("请检测参数", c) + } else if err != nil { + response.FailWithMessage("发生错误", c) + } else { + //发送删除通知信息 + var chanInfo = spec.ChanCommon{ + Type: enums.ChanComTypeTunnel, + OpType: enums.OP_TYPE_DELETE, + OldContent: tunnel, + } + global.GWAF_CHAN_COMMON_MSG <- chanInfo + response.OkWithMessage("删除成功", c) + } + } else { + response.FailWithMessage("解析失败", c) + } +} + +func (w *WafTunnelApi) ModifyApi(c *gin.Context) { + var req request.WafTunnelEditReq + err := c.ShouldBindJSON(&req) + if err == nil { + oldTunnel := wafTunnelService.GetDetailByIdApi(req.Id) + err = wafTunnelService.ModifyApi(req) + if err != nil { + response.FailWithMessage("编辑发生错误"+err.Error(), c) + } else { + newTunnel := wafTunnelService.GetDetailByIdApi(req.Id) + + //发送修改通知信息 + var chanInfo = spec.ChanCommon{ + Type: enums.ChanComTypeTunnel, + OpType: enums.OP_TYPE_UPDATE, + Content: newTunnel, + OldContent: oldTunnel, + } + global.GWAF_CHAN_COMMON_MSG <- chanInfo + response.OkWithMessage("编辑成功", c) + } + } else { + response.FailWithMessage("解析失败", c) + } +} + +// GetTunnelConnectionsApi 获取隧道连接信息API +func (w *WafTunnelApi) GetTunnelConnectionsApi(c *gin.Context) { + var req request.WafTunnelConnReq + err := c.ShouldBind(&req) + if err == nil { + tunnel, err := wafTunnelService.GetTunnelConnections(req.ID) + if err != nil { + response.FailWithMessage("获取隧道连接信息失败: "+err.Error(), c) + return + } + // 获取端口列表 + portStrArray := strings.Split(tunnel.Port, ",") + portInfoList := make([]map[string]interface{}, 0) + + for _, portStr := range portStrArray { + portStr = strings.TrimSpace(portStr) + if portStr == "" { + continue + } + + port, err := strconv.Atoi(portStr) + if err != nil { + continue + } + + // 从全局变量中获取连接信息 + tcpSourceCount := 0 + tcpTargetCount := 0 + udpSourceCount := 0 + udpTargetCount := 0 + + // TCP来源连接IP列表 + tcpSourceIPs := []map[string]string{} + // UDP来源连接IP列表 + udpSourceIPs := []map[string]string{} + + // 获取TCP连接数和IP列表 + if globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE != nil { + tcpSourceCount = globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.TCPConnections.GetPortConnsCountByType(port, waftunnelmodel.ConnTypeSource) + tcpTargetCount = globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.TCPConnections.GetPortConnsCountByType(port, waftunnelmodel.ConnTypeTarget) + udpSourceCount = globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.UDPConnections.GetPortConnsCountByType(port, waftunnelmodel.ConnTypeSource) + udpTargetCount = globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.UDPConnections.GetPortConnsCountByType(port, waftunnelmodel.ConnTypeTarget) + + // 获取TCP来源连接IP列表 + tcpSourceIPList := globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.TCPConnections.GetPortConnsInfo(port, waftunnelmodel.ConnTypeSource) + for _, ip := range tcpSourceIPList { + // 获取IP归属地区 + region := utils.GetCountry(ip) + tcpSourceIPs = append(tcpSourceIPs, map[string]string{ + "ip": ip, + "region": fmt.Sprintf("%v", region), + }) + } + + // 获取UDP来源连接IP列表 + udpSourceIPList := globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.UDPConnections.GetPortConnsInfo(port, waftunnelmodel.ConnTypeSource) + for _, ip := range udpSourceIPList { + // 获取IP归属地区 + region := utils.GetCountry(ip) + udpSourceIPs = append(udpSourceIPs, map[string]string{ + "ip": ip, + "region": fmt.Sprintf("%v", region), + }) + } + } + + portInfo := map[string]interface{}{ + "port": port, + "tcp_source_count": tcpSourceCount, + "tcp_target_count": tcpTargetCount, + "udp_source_count": udpSourceCount, + "udp_target_count": udpTargetCount, + "tcp_source_ips": tcpSourceIPs, + "udp_source_ips": udpSourceIPs, + } + + portInfoList = append(portInfoList, portInfo) + } + + // 获取连接的IP列表 + connInfo := map[string]interface{}{ + "tunnel_info": tunnel, + "port_info": portInfoList, + "protocol": tunnel.Protocol, + } + + response.OkWithDetailed(connInfo, "获取成功", c) + } else { + response.FailWithMessage("解析失败", c) + } +} diff --git a/codetemplete/code_gen_test.go b/codetemplete/code_gen_test.go index ec5db8d..5c1331a 100644 --- a/codetemplete/code_gen_test.go +++ b/codetemplete/code_gen_test.go @@ -9,8 +9,7 @@ import ( func TestCodeGeneration(t *testing.T) { // 唯一校验定义字段信息的字符串 fieldDefs := []string{ - "HostCode:host_code:string", - "RuleName:rule_name:string", + "Name:name:string", } // 构造 `uniFields` 列表 @@ -28,6 +27,6 @@ func TestCodeGeneration(t *testing.T) { } } - fields := GetStructFields(model.CacheRule{}) - CodeGeneration("CacheRule", fields, uniFields) + fields := GetStructFields(model.Tunnel{}) + CodeGeneration("Tunnel", fields, uniFields) } diff --git a/enums/chan_common_enum.go b/enums/chan_common_enum.go new file mode 100644 index 0000000..2ac0bb7 --- /dev/null +++ b/enums/chan_common_enum.go @@ -0,0 +1,6 @@ +package enums + +const ( + //隧道 + ChanComTypeTunnel = iota +) diff --git a/enums/op_type.go b/enums/op_type.go new file mode 100644 index 0000000..2c0ac6a --- /dev/null +++ b/enums/op_type.go @@ -0,0 +1,7 @@ +package enums + +const ( + OP_TYPE_NEW = iota + OP_TYPE_UPDATE + OP_TYPE_DELETE +) diff --git a/global/global.go b/global/global.go index 1e5848d..6a8b976 100644 --- a/global/global.go +++ b/global/global.go @@ -43,6 +43,7 @@ var ( GWAF_RUNTIME_CURRENT_EXEPATH string = "" //当前程序运行路径 GWAF_RUNTIME_CURRENT_WEBPORT string = "" //当前程序所占用端口 + GWAF_RUNTIME_CURRENT_TUNNELPORT string = "" //当前程序所占用隧道端口 GWAF_RUNTIME_CURRENT_EXPORT_DB_LOG_FILE_PATH string = "" //生成的日志导出文件路径 GWAF_RUNTIME_SSL_EXPIRE_CHECK bool = false //SSL过期检测是否正在运行 @@ -81,6 +82,7 @@ var ( GWAF_CHAN_HOST = make(chan model.Hosts, 10) //主机链 GWAF_CHAN_ENGINE = make(chan int, 10) //引擎链 GWAF_CHAN_MSG = make(chan spec.ChanCommonHost, 10) //全局通讯包 + GWAF_CHAN_COMMON_MSG = make(chan spec.ChanCommon, 10) //全局共用通讯包 GWAF_CHAN_UPDATE = make(chan int, 10) //升级后处理链 GWAF_CHAN_SENSITIVE = make(chan int, 10) //敏感词处理链 GWAF_CHAN_SSL = make(chan string, 10) //证书处理链 diff --git a/globalobj/globalobj.go b/globalobj/globalobj.go index d8af3fa..8e74aac 100644 --- a/globalobj/globalobj.go +++ b/globalobj/globalobj.go @@ -3,13 +3,15 @@ package globalobj import ( "SamWaf/wafenginecore" "SamWaf/waftask" + "SamWaf/waftunnelengine" ) var ( /*** 本地对象映射关系 */ - GWAF_RUNTIME_OBJ_WAF_ENGINE *wafenginecore.WafEngine //当前引擎对象 - GWAF_RUNTIME_OBJ_WAF_TaskRegistry *waftask.TaskRegistry // 任务执行器 - GWAF_RUNTIME_OBJ_WAF_TaskScheduler *waftask.TaskScheduler // 任务计划 + GWAF_RUNTIME_OBJ_WAF_ENGINE *wafenginecore.WafEngine //当前引擎对象 + GWAF_RUNTIME_OBJ_TUNNEL_ENGINE *waftunnelengine.WafTunnelEngine //当前tunnel引擎对象 + GWAF_RUNTIME_OBJ_WAF_TaskRegistry *waftask.TaskRegistry // 任务执行器 + GWAF_RUNTIME_OBJ_WAF_TaskScheduler *waftask.TaskScheduler // 任务计划 ) diff --git a/main.go b/main.go index fa84824..a9c8c31 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ import ( "SamWaf/wafsnowflake" "SamWaf/wafssl" "SamWaf/waftask" + "SamWaf/waftunnelengine" "SamWaf/webplugin" "crypto/tls" "embed" @@ -287,6 +288,9 @@ func (m *wafSystenService) run() { http.Handle("/", globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE) globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.StartWaf() + //启动隧道 + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE = waftunnelengine.NewWafTunnelEngine() + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.StartTunnel() //启动管理界面 webmanager = &wafmangeweb.WafWebManager{LogName: "WebManager"} go func() { @@ -538,6 +542,33 @@ func (m *wafSystenService) run() { } } break + case common := <-global.GWAF_CHAN_COMMON_MSG: + if common.Type == enums.ChanComTypeTunnel { + //隧道类型 + switch common.OpType { + case enums.OP_TYPE_NEW: + tunnelNew := common.Content.(model.Tunnel) + netRunTimes := globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.LoadTunnel(tunnelNew) + for _, netRunTime := range netRunTimes { + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.StartTunnelServer(netRunTime) + } + break + case enums.OP_TYPE_UPDATE: + // 获取修改前的隧道信息 + tunnelOld := common.OldContent.(model.Tunnel) + // 获取修改后的隧道信息 + tunnelNew := common.Content.(model.Tunnel) + netRunTimes := globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.EditTunnel(tunnelOld, tunnelNew) + for _, netRunTime := range netRunTimes { + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.StartTunnelServer(netRunTime) + } + break + case enums.OP_TYPE_DELETE: + tunnelDelete := common.OldContent.(model.Tunnel) + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.RemoveTunnel(tunnelDelete) + break + } + } case engineStatus := <-global.GWAF_CHAN_ENGINE: if engineStatus == 1 { zlog.Info("准备关闭WAF引擎") @@ -545,6 +576,11 @@ func (m *wafSystenService) run() { zlog.Info("准备启动WAF引擎") globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.StartWaf() + zlog.Info("准备关闭隧道引擎") + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.CloseTunnel() + zlog.Info("准备启动隧道引擎") + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.StartTunnel() + } break case host := <-global.GWAF_CHAN_HOST: @@ -630,6 +666,10 @@ func (m *wafSystenService) stopSamWaf() { globalobj.GWAF_RUNTIME_OBJ_WAF_ENGINE.CloseWaf() zlog.Info("Shutdown SamWaf Engine finished") + zlog.Info("Shutdown SamWaf Tunnel Engine...") + globalobj.GWAF_RUNTIME_OBJ_TUNNEL_ENGINE.CloseTunnel() + zlog.Info("Shutdown SamWaf Tunnel Engine finished") + zlog.Info("Shutdown SamWaf Queue Processors...") // 关闭信号通道,通知所有队列处理协程退出 close(global.GWAF_QUEUE_SHUTDOWN_SIGNAL) diff --git a/model/request/waf_tunnel_req.go b/model/request/waf_tunnel_req.go new file mode 100644 index 0000000..b138ed3 --- /dev/null +++ b/model/request/waf_tunnel_req.go @@ -0,0 +1,54 @@ +package request + +import "SamWaf/model/common/request" + +type WafTunnelAddReq struct { + Code string `json:"code" form:"code"` + Name string `json:"name" form:"name"` + Port string `json:"port" form:"port"` + Protocol string `json:"protocol" form:"protocol"` + RemotePort int `json:"remote_port" form:"remote_port"` + RemoteIp string `json:"remote_ip" form:"remote_ip"` + AllowIp string `json:"allow_ip" form:"allow_ip"` + DenyIp string `json:"deny_ip" form:"deny_ip"` + StartStatus int `json:"start_status" form:"start_status"` + ConnTimeout int `json:"conn_timeout" form:"conn_timeout"` + ReadTimeout int `json:"read_timeout" form:"read_timeout"` + WriteTimeout int `json:"write_timeout" form:"write_timeout"` + MaxInConnect int `json:"max_in_connect" form:"max_in_connect"` + MaxOutConnect int `json:"max_out_connect" form:"max_out_connect"` + Remark string `json:"remark" form:"remark"` +} +type WafTunnelEditReq struct { + Id string `json:"id"` + + Code string `json:"code" form:"code"` + Name string `json:"name" form:"name"` + Port string `json:"port" form:"port"` + Protocol string `json:"protocol" form:"protocol"` + RemotePort int `json:"remote_port" form:"remote_port"` + RemoteIp string `json:"remote_ip" form:"remote_ip"` + AllowIp string `json:"allow_ip" form:"allow_ip"` + DenyIp string `json:"deny_ip" form:"deny_ip"` + StartStatus int `json:"start_status" form:"start_status"` + ConnTimeout int `json:"conn_timeout" form:"conn_timeout"` + ReadTimeout int `json:"read_timeout" form:"read_timeout"` + WriteTimeout int `json:"write_timeout" form:"write_timeout"` + MaxInConnect int `json:"max_in_connect" form:"max_in_connect"` + MaxOutConnect int `json:"max_out_connect" form:"max_out_connect"` + Remark string `json:"remark" form:"remark"` +} +type WafTunnelDetailReq struct { + Id string `json:"id" form:"id"` +} +type WafTunnelDelReq struct { + Id string `json:"id" form:"id"` +} +type WafTunnelSearchReq struct { + request.PageInfo +} + +// WafTunnelConnReq 通过ID获取隧道连接请求 +type WafTunnelConnReq struct { + ID string `json:"id" form:"id"` +} diff --git a/model/spec/chan_common.go b/model/spec/chan_common.go index c40e2f3..24e1a1a 100644 --- a/model/spec/chan_common.go +++ b/model/spec/chan_common.go @@ -6,3 +6,11 @@ type ChanCommonHost struct { Content interface{} OldContent interface{} } + +// ChanCommon 通用通道 +type ChanCommon struct { + Type int //某种类型 1 比如代表隧道 + OpType int //操作类型 1代表新增 2代表删除 3代表修改 + Content interface{} //新增的内容 + OldContent interface{} //修改前的内容 +} diff --git a/model/tunnel.go b/model/tunnel.go new file mode 100644 index 0000000..4df1bce --- /dev/null +++ b/model/tunnel.go @@ -0,0 +1,25 @@ +package model + +import ( + "SamWaf/model/baseorm" +) + +// Tunnel 隧道 +type Tunnel struct { + baseorm.BaseOrm + Code string `json:"code"` //唯一码 + Name string `json:"name"` //名称 + Port string `json:"port"` //端口可以多个,号隔开 + Protocol string `json:"protocol"` //协议 tcp,udp + RemotePort int `json:"remote_port"` //远端端口 + RemoteIp string `json:"remote_ip"` //远端指定IP + AllowIp string `json:"allow_ip"` //允许访问的IP ,号隔开 + DenyIp string `json:"deny_ip"` //拒绝访问的IP ,号隔开 + StartStatus int `json:"start_status"` //启动状态 如果是1 启动 ; 如果是0 不启动 + ConnTimeout int `json:"conn_timeout"` // 连接超时时间,单位秒 60s + ReadTimeout int `json:"read_timeout"` // 读取超时时间,单位秒 0 表示不限制 + WriteTimeout int `json:"write_timeout"` // 写入超时时间,单位秒 0 表示不限制 + MaxInConnect int `json:"max_in_connect"` // 最大入站连接数 0 表示不限制 + MaxOutConnect int `json:"max_out_connect"` // 最大出站连接数 0 表示不限制 + Remark string `json:"remark"` //备注 +} diff --git a/model/waftunnelmodel/safe_conn_map.go b/model/waftunnelmodel/safe_conn_map.go new file mode 100644 index 0000000..6a4dbc7 --- /dev/null +++ b/model/waftunnelmodel/safe_conn_map.go @@ -0,0 +1,313 @@ +package waftunnelmodel + +import ( + "net" + "sync" + "time" +) + +// 连接类型常量 +const ( + ConnTypeSource = iota // 来源连接(客户端连接) + ConnTypeTarget // 目标连接(服务器连接) +) + +// ConnInfo 连接信息结构体 +type ConnInfo struct { + ConnType int // 连接类型:0-来源连接,1-目标连接 + CreateTime time.Time // 连接创建时间 +} + +// SafeTCPConnMap 线程安全的TCP连接管理Map +type SafeTCPConnMap struct { + mu sync.RWMutex + items map[int]map[net.Conn]ConnInfo // 端口号 -> 连接 -> 连接信息 +} + +// NewSafeTCPConnMap 创建新的TCP连接安全Map +func NewSafeTCPConnMap() *SafeTCPConnMap { + return &SafeTCPConnMap{ + items: make(map[int]map[net.Conn]ConnInfo), + } +} + +// AddConn 添加连接 +func (m *SafeTCPConnMap) AddConn(port int, conn net.Conn, connType int) { + m.mu.Lock() + defer m.mu.Unlock() + + if _, exists := m.items[port]; !exists { + m.items[port] = make(map[net.Conn]ConnInfo) + } + + m.items[port][conn] = ConnInfo{ + ConnType: connType, + CreateTime: time.Now(), + } +} + +// RemoveConn 移除连接 +func (m *SafeTCPConnMap) RemoveConn(port int, conn net.Conn) { + m.mu.Lock() + defer m.mu.Unlock() + + if conns, exists := m.items[port]; exists { + delete(conns, conn) + } +} + +// ClosePortConns 关闭指定端口的所有连接 +func (m *SafeTCPConnMap) ClosePortConns(port int) { + m.mu.Lock() + defer m.mu.Unlock() + + if conns, exists := m.items[port]; exists { + for conn := range conns { + conn.Close() + } + delete(m.items, port) + } +} + +// CloseAllConns 关闭所有连接 +func (m *SafeTCPConnMap) CloseAllConns() { + m.mu.Lock() + defer m.mu.Unlock() + + for _, conns := range m.items { + for conn := range conns { + conn.Close() + } + } + + // 清空map + m.items = make(map[int]map[net.Conn]ConnInfo) +} + +// GetPortConnsCount 获取指定端口的连接数 +func (m *SafeTCPConnMap) GetPortConnsCount(port int) int { + m.mu.RLock() + defer m.mu.RUnlock() + + if conns, exists := m.items[port]; exists { + return len(conns) + } + return 0 +} + +// GetPortConnsCountByType 获取指定端口指定类型的连接数 +func (m *SafeTCPConnMap) GetPortConnsCountByType(port int, connType int) int { + m.mu.RLock() + defer m.mu.RUnlock() + + count := 0 + if conns, exists := m.items[port]; exists { + for _, info := range conns { + if info.ConnType == connType { + count++ + } + } + } + return count +} + +// GetAllConnsCount 获取所有连接数 +func (m *SafeTCPConnMap) GetAllConnsCount() int { + m.mu.RLock() + defer m.mu.RUnlock() + + total := 0 + for _, conns := range m.items { + total += len(conns) + } + return total +} + +// GetAllConnsCountByType 获取所有指定类型的连接数 +func (m *SafeTCPConnMap) GetAllConnsCountByType(connType int) int { + m.mu.RLock() + defer m.mu.RUnlock() + + total := 0 + for _, conns := range m.items { + for _, info := range conns { + if info.ConnType == connType { + total++ + } + } + } + return total +} + +// SafeUDPConnMap 线程安全的UDP连接管理Map +type SafeUDPConnMap struct { + mu sync.RWMutex + items map[int]map[*net.UDPConn]ConnInfo // 端口号 -> 连接 -> 连接信息 +} + +// NewSafeUDPConnMap 创建新的UDP连接安全Map +func NewSafeUDPConnMap() *SafeUDPConnMap { + return &SafeUDPConnMap{ + items: make(map[int]map[*net.UDPConn]ConnInfo), + } +} + +// AddConn 添加连接 +func (m *SafeUDPConnMap) AddConn(port int, conn *net.UDPConn, connType int) { + m.mu.Lock() + defer m.mu.Unlock() + + if _, exists := m.items[port]; !exists { + m.items[port] = make(map[*net.UDPConn]ConnInfo) + } + + m.items[port][conn] = ConnInfo{ + ConnType: connType, + CreateTime: time.Now(), + } +} + +// RemoveConn 移除连接 +func (m *SafeUDPConnMap) RemoveConn(port int, conn *net.UDPConn) { + m.mu.Lock() + defer m.mu.Unlock() + + if conns, exists := m.items[port]; exists { + delete(conns, conn) + } +} + +// ClosePortConns 关闭指定端口的所有连接 +func (m *SafeUDPConnMap) ClosePortConns(port int) { + m.mu.Lock() + defer m.mu.Unlock() + + if conns, exists := m.items[port]; exists { + for conn := range conns { + conn.Close() + } + delete(m.items, port) + } +} + +// CloseAllConns 关闭所有连接 +func (m *SafeUDPConnMap) CloseAllConns() { + m.mu.Lock() + defer m.mu.Unlock() + + for _, conns := range m.items { + for conn := range conns { + conn.Close() + } + } + + // 清空map + m.items = make(map[int]map[*net.UDPConn]ConnInfo) +} + +// GetPortConnsCount 获取指定端口的连接数 +func (m *SafeUDPConnMap) GetPortConnsCount(port int) int { + m.mu.RLock() + defer m.mu.RUnlock() + + if conns, exists := m.items[port]; exists { + return len(conns) + } + return 0 +} + +// GetPortConnsCountByType 获取指定端口指定类型的连接数 +func (m *SafeUDPConnMap) GetPortConnsCountByType(port int, connType int) int { + m.mu.RLock() + defer m.mu.RUnlock() + + count := 0 + if conns, exists := m.items[port]; exists { + for _, info := range conns { + if info.ConnType == connType { + count++ + } + } + } + return count +} + +// GetAllConnsCount 获取所有连接数 +func (m *SafeUDPConnMap) GetAllConnsCount() int { + m.mu.RLock() + defer m.mu.RUnlock() + + total := 0 + for _, conns := range m.items { + total += len(conns) + } + return total +} + +// GetAllConnsCountByType 获取所有指定类型的连接数 +func (m *SafeUDPConnMap) GetAllConnsCountByType(connType int) int { + m.mu.RLock() + defer m.mu.RUnlock() + + total := 0 + for _, conns := range m.items { + for _, info := range conns { + if info.ConnType == connType { + total++ + } + } + } + return total +} + +// GetPortConnsInfo 获取指定端口的连接信息 +func (m *SafeTCPConnMap) GetPortConnsInfo(port int, connType int) []string { + m.mu.RLock() + defer m.mu.RUnlock() + + ipList := make([]string, 0) + if conns, exists := m.items[port]; exists { + for conn, info := range conns { + if info.ConnType == connType { + // 获取连接的远程地址 + if remoteAddr := conn.RemoteAddr(); remoteAddr != nil { + // 从地址中提取IP(去除端口) + host, _, err := net.SplitHostPort(remoteAddr.String()) + if err == nil { + ipList = append(ipList, host) + } else { + // 如果解析失败,使用原始地址 + ipList = append(ipList, remoteAddr.String()) + } + } + } + } + } + return ipList +} + +// GetPortConnsInfo 获取指定端口的连接信息 +func (m *SafeUDPConnMap) GetPortConnsInfo(port int, connType int) []string { + m.mu.RLock() + defer m.mu.RUnlock() + + ipList := make([]string, 0) + if conns, exists := m.items[port]; exists { + for conn, info := range conns { + if info.ConnType == connType { + // 获取连接的远程地址 + if remoteAddr := conn.RemoteAddr(); remoteAddr != nil { + // 从地址中提取IP(去除端口) + host, _, err := net.SplitHostPort(remoteAddr.String()) + if err == nil { + ipList = append(ipList, host) + } else { + // 如果解析失败,使用原始地址 + ipList = append(ipList, remoteAddr.String()) + } + } + } + } + } + return ipList +} diff --git a/model/waftunnelmodel/safe_map.go b/model/waftunnelmodel/safe_map.go new file mode 100644 index 0000000..95ac637 --- /dev/null +++ b/model/waftunnelmodel/safe_map.go @@ -0,0 +1,143 @@ +package waftunnelmodel + +import ( + "sync" +) + +// SafeTunnelMap 线程安全的TunnelTarget Map +type SafeTunnelMap struct { + mu sync.RWMutex + items map[string]*TunnelSafe +} + +// NewSafeTunnelMap 创建新的安全Map +func NewSafeTunnelMap() *SafeTunnelMap { + return &SafeTunnelMap{ + items: make(map[string]*TunnelSafe), + } +} + +// Get 获取值 +func (m *SafeTunnelMap) Get(key string) (*TunnelSafe, bool) { + m.mu.RLock() + defer m.mu.RUnlock() + val, ok := m.items[key] + return val, ok +} + +// Set 设置值 +func (m *SafeTunnelMap) Set(key string, value *TunnelSafe) { + m.mu.Lock() + defer m.mu.Unlock() + m.items[key] = value +} + +// Delete 删除值 +func (m *SafeTunnelMap) Delete(key string) { + m.mu.Lock() + defer m.mu.Unlock() + delete(m.items, key) +} + +// Range 遍历Map +func (m *SafeTunnelMap) Range(f func(key string, value *TunnelSafe) bool) { + m.mu.RLock() + defer m.mu.RUnlock() + for k, v := range m.items { + if !f(k, v) { + break + } + } +} + +// Len 获取Map长度 +func (m *SafeTunnelMap) Len() int { + m.mu.RLock() + defer m.mu.RUnlock() + return len(m.items) +} + +// GetAll 获取所有值 +func (m *SafeTunnelMap) GetAll() map[string]*TunnelSafe { + m.mu.RLock() + defer m.mu.RUnlock() + result := make(map[string]*TunnelSafe, len(m.items)) + for k, v := range m.items { + result[k] = v + } + return result +} +func (m *SafeTunnelMap) Clear() { + m.mu.Lock() + defer m.mu.Unlock() + m.items = make(map[string]*TunnelSafe) +} + +// SafeNetMap 线程安全的NetListerOnline Map +type SafeNetMap struct { + mu sync.RWMutex + items map[string]NetRunTime +} + +// NewSafeNetMap 创建新的安全Map +func NewSafeNetMap() *SafeNetMap { + return &SafeNetMap{ + items: make(map[string]NetRunTime), + } +} + +// Get 获取值 +func (m *SafeNetMap) Get(key string) (NetRunTime, bool) { + m.mu.RLock() + defer m.mu.RUnlock() + val, ok := m.items[key] + return val, ok +} + +// Set 设置值 +func (m *SafeNetMap) Set(key string, value NetRunTime) { + m.mu.Lock() + defer m.mu.Unlock() + m.items[key] = value +} + +// Delete 删除值 +func (m *SafeNetMap) Delete(key string) { + m.mu.Lock() + defer m.mu.Unlock() + delete(m.items, key) +} + +// Range 遍历Map +func (m *SafeNetMap) Range(f func(key string, value NetRunTime) bool) { + m.mu.RLock() + defer m.mu.RUnlock() + for k, v := range m.items { + if !f(k, v) { + break + } + } +} + +// Len 获取Map长度 +func (m *SafeNetMap) Len() int { + m.mu.RLock() + defer m.mu.RUnlock() + return len(m.items) +} + +// GetAll 获取所有值 +func (m *SafeNetMap) GetAll() map[string]NetRunTime { + m.mu.RLock() + defer m.mu.RUnlock() + result := make(map[string]NetRunTime, len(m.items)) + for k, v := range m.items { + result[k] = v + } + return result +} +func (m *SafeNetMap) Clear() { + m.mu.Lock() + defer m.mu.Unlock() + m.items = make(map[string]NetRunTime) +} diff --git a/model/waftunnelmodel/tunnel_runtime.go b/model/waftunnelmodel/tunnel_runtime.go new file mode 100644 index 0000000..67098d9 --- /dev/null +++ b/model/waftunnelmodel/tunnel_runtime.go @@ -0,0 +1,9 @@ +package waftunnelmodel + +type NetRunTime struct { + //tcp udp + ServerType string + Port int + Status int // 0 是启动完成 ,1 是新增,2 是编辑 3,是删除 + Svr interface{} // 支持任何网络连接类型 +} diff --git a/model/waftunnelmodel/tunnelsafe.go b/model/waftunnelmodel/tunnelsafe.go new file mode 100644 index 0000000..584a6ec --- /dev/null +++ b/model/waftunnelmodel/tunnelsafe.go @@ -0,0 +1,12 @@ +package waftunnelmodel + +import ( + "SamWaf/model" + "sync" +) + +// TunnelSafe 隧道安全配置 +type TunnelSafe struct { + Mux sync.Mutex //互斥锁 + Tunnel model.Tunnel +} diff --git a/router/entrance.go b/router/entrance.go index 806ed56..f312147 100644 --- a/router/entrance.go +++ b/router/entrance.go @@ -37,6 +37,7 @@ type ApiGroup struct { AnalysisRouter WafPrivateInfoRouter WafCacheRuleRouter + WafTunnelRouter } type PublicApiGroup struct { LoginRouter diff --git a/router/waf_tunnel_router.go b/router/waf_tunnel_router.go new file mode 100644 index 0000000..1f3a1db --- /dev/null +++ b/router/waf_tunnel_router.go @@ -0,0 +1,20 @@ +package router + +import ( + "SamWaf/api" + "github.com/gin-gonic/gin" +) + +type WafTunnelRouter struct { +} + +func (receiver *WafTunnelRouter) InitWafTunnelRouter(group *gin.RouterGroup) { + api := api.APIGroupAPP.WafTunnelApi + router := group.Group("") + router.POST("/samwaf/tunnel/tunnel/add", api.AddApi) + router.POST("/samwaf/tunnel/tunnel/list", api.GetListApi) + router.GET("/samwaf/tunnel/tunnel/detail", api.GetDetailApi) + router.POST("/samwaf/tunnel/tunnel/edit", api.ModifyApi) + router.GET("/samwaf/tunnel/tunnel/del", api.DelApi) + router.GET("/samwaf/tunnel/tunnel/connections", api.GetTunnelConnectionsApi) +} diff --git a/service/waf_service/waf_stat.go b/service/waf_service/waf_stat.go index 1cdfeab..58bc985 100644 --- a/service/waf_service/waf_stat.go +++ b/service/waf_service/waf_stat.go @@ -246,6 +246,7 @@ func (receiver *WafStatService) StatHomeRumtimeSysinfo() []response2.WafNameValu data = append(data, response2.WafNameValue{Name: "当前队列数", Value: fmt.Sprintf("主数据:%v 日志数据:%v 统计数据:%v 消息队列:%v", global.GQEQUE_DB.Size(), global.GQEQUE_LOG_DB.Size(), global.GQEQUE_STATS_DB.Size(), global.GQEQUE_MESSAGE_DB.Size())}) data = append(data, response2.WafNameValue{Name: "当前日志队列处理QPS", Value: fmt.Sprintf("%v", atomic.LoadUint64(&global.GWAF_RUNTIME_LOG_PROCESS))}) data = append(data, response2.WafNameValue{Name: "当前web端口使用列表", Value: fmt.Sprintf("%v", global.GWAF_RUNTIME_CURRENT_WEBPORT)}) + data = append(data, response2.WafNameValue{Name: "当前隧道端口使用列表", Value: fmt.Sprintf("%v", global.GWAF_RUNTIME_CURRENT_TUNNELPORT)}) lastTimeStr := time.Unix(0, global.GWAF_LAST_TIME_UNIX*int64(time.Millisecond)).Format("2006-01-02 15:04:05") data = append(data, response2.WafNameValue{Name: "最后统计时间", Value: fmt.Sprintf("%v (%v)", lastTimeStr, global.GWAF_LAST_TIME_UNIX)}) diff --git a/service/waf_service/waf_tunnel_service.go b/service/waf_service/waf_tunnel_service.go new file mode 100644 index 0000000..5f7f23d --- /dev/null +++ b/service/waf_service/waf_tunnel_service.go @@ -0,0 +1,171 @@ +package waf_service + +import ( + "SamWaf/common/uuid" + "SamWaf/customtype" + "SamWaf/global" + "SamWaf/model" + "SamWaf/model/baseorm" + "SamWaf/model/request" + "errors" + "time" +) + +type WafTunnelService struct{} + +var WafTunnelServiceApp = new(WafTunnelService) + +func (receiver *WafTunnelService) AddApi(req request.WafTunnelAddReq) (*model.Tunnel, error) { + var bean = &model.Tunnel{ + BaseOrm: baseorm.BaseOrm{ + Id: uuid.GenUUID(), + USER_CODE: global.GWAF_USER_CODE, + Tenant_ID: global.GWAF_TENANT_ID, + CREATE_TIME: customtype.JsonTime(time.Now()), + UPDATE_TIME: customtype.JsonTime(time.Now()), + }, + + Code: req.Code, + Name: req.Name, + Port: req.Port, + Protocol: req.Protocol, + RemotePort: req.RemotePort, + RemoteIp: req.RemoteIp, + AllowIp: req.AllowIp, + DenyIp: req.DenyIp, + StartStatus: req.StartStatus, + ConnTimeout: req.ConnTimeout, + ReadTimeout: req.ReadTimeout, + WriteTimeout: req.WriteTimeout, + MaxInConnect: req.MaxInConnect, + MaxOutConnect: req.MaxOutConnect, + Remark: req.Remark, + } + if bean.Code == "" { + bean.Code = bean.Id + } + global.GWAF_LOCAL_DB.Create(bean) + return bean, nil +} + +func (receiver *WafTunnelService) CheckIsExistApi(req request.WafTunnelAddReq) int { + var total int64 = 0 + /*where条件*/ + var whereField = "" + var whereValues []interface{} + //where字段 + whereField = "" + + if len(req.Name) > 0 { + if len(whereField) > 0 { + whereField = whereField + " and " + } + whereField = whereField + " name=? " + } + + //where字段赋值 + + if len(req.Name) > 0 { + if len(whereField) > 0 { + whereValues = append(whereValues, req.Name) + } + } + + global.GWAF_LOCAL_DB.Model(&model.Tunnel{}).Where(whereField, whereValues...).Count(&total) + return int(total) +} + +func (receiver *WafTunnelService) ModifyApi(req request.WafTunnelEditReq) error { + // 根据唯一字段生成查询条件(只有在UniFields不为空时才进行存在性检查) + + var total int64 = 0 + /*where条件*/ + var whereField = "" + var whereValues []interface{} + //where字段 + whereField = "" + + if len(req.Name) > 0 { + if len(whereField) > 0 { + whereField = whereField + " and " + } + whereField = whereField + " name=? " + } + + //where字段赋值 + + if len(req.Name) > 0 { + whereValues = append(whereValues, req.Name) + } + + global.GWAF_LOCAL_DB.Model(&model.Tunnel{}).Where(whereField, whereValues...).Count(&total) + // 查询是否已存在记录 + var bean model.Tunnel + global.GWAF_LOCAL_DB.Model(&model.Tunnel{}).Where(whereField, whereValues...).Limit(1).Find(&bean) + + if int(total) > 0 && bean.Id != "" && bean.Id != req.Id { + return errors.New("当前记录已经存在") + } + + beanMap := map[string]interface{}{ + + "Code": req.Code, + "Name": req.Name, + "Port": req.Port, + "Protocol": req.Protocol, + "RemotePort": req.RemotePort, + "RemoteIp": req.RemoteIp, + "AllowIp": req.AllowIp, + "DenyIp": req.DenyIp, + "StartStatus": req.StartStatus, + "ConnTimeout": req.ConnTimeout, + "ReadTimeout": req.ReadTimeout, + "WriteTimeout": req.WriteTimeout, + "MaxInConnect": req.MaxInConnect, + "MaxOutConnect": req.MaxOutConnect, + "Remark": req.Remark, + + "UPDATE_TIME": customtype.JsonTime(time.Now()), + } + err := global.GWAF_LOCAL_DB.Model(model.Tunnel{}).Where("id = ?", req.Id).Updates(beanMap).Error + + return err +} +func (receiver *WafTunnelService) GetDetailApi(req request.WafTunnelDetailReq) model.Tunnel { + var bean model.Tunnel + global.GWAF_LOCAL_DB.Where("id=?", req.Id).Find(&bean) + return bean +} +func (receiver *WafTunnelService) GetDetailByIdApi(id string) model.Tunnel { + var bean model.Tunnel + global.GWAF_LOCAL_DB.Where("id=?", id).Find(&bean) + return bean +} +func (receiver *WafTunnelService) GetListApi(req request.WafTunnelSearchReq) ([]model.Tunnel, int64, error) { + var list []model.Tunnel + var total int64 = 0 + global.GWAF_LOCAL_DB.Model(&model.Tunnel{}).Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list) + global.GWAF_LOCAL_DB.Model(&model.Tunnel{}).Count(&total) + + return list, total, nil +} +func (receiver *WafTunnelService) DelApi(req request.WafTunnelDelReq) error { + var bean model.Tunnel + err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error + if err != nil { + return err + } + err = global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(model.Tunnel{}).Error + return err +} + +// GetTunnelConnections 获取隧道连接信息 +func (receiver *WafTunnelService) GetTunnelConnections(id string) (model.Tunnel, error) { + var tunnel model.Tunnel + err := global.GWAF_LOCAL_DB.Where("id = ?", id).First(&tunnel).Error + if err != nil { + return model.Tunnel{}, err + } + + return tunnel, nil +} diff --git a/utils/common.go b/utils/common.go index 7013549..130af61 100644 --- a/utils/common.go +++ b/utils/common.go @@ -198,16 +198,6 @@ func CloseIPDatabase() { } } -// PortCheck 检查端口是否可用,可用-true 不可用-false -func PortCheck(port int) bool { - conn, err := net.DialTimeout("tcp", fmt.Sprintf(":%d", port), time.Second) - if err != nil { - return true // Port is available - } - defer conn.Close() - return false // Port is not available -} - /* * 通过ip段(CIDR)来查询是否在很多CIDR段内 diff --git a/utils/net_utils.go b/utils/net_utils.go new file mode 100644 index 0000000..85ef9e2 --- /dev/null +++ b/utils/net_utils.go @@ -0,0 +1,39 @@ +package utils + +import ( + "fmt" + "net" + "strconv" + "time" +) + +// PortCheck 检查端口是否可用,可用-true 不可用-false +func PortCheck(port int) bool { + conn, err := net.DialTimeout("tcp", fmt.Sprintf(":%d", port), time.Second) + if err != nil { + return true // Port is available + } + defer conn.Close() + return false // Port is not available +} + +// TCP端口检测 +func TCPPortCheck(port int) bool { + conn, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + return true + } + defer conn.Close() + return false +} + +// UDP端口检测 +func UDPPortCheck(port int) bool { + addr, _ := net.ResolveUDPAddr("udp", ":"+strconv.Itoa(port)) + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return true + } + defer conn.Close() + return false +} diff --git a/wafdb/localdb.go b/wafdb/localdb.go index aeeca9a..ee4817b 100644 --- a/wafdb/localdb.go +++ b/wafdb/localdb.go @@ -152,6 +152,9 @@ func InitCoreDb(currentDir string) (bool, error) { //缓存规则 db.AutoMigrate(&model.CacheRule{}) + //隧道 + db.AutoMigrate(&model.Tunnel{}) + global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query) global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:update").Register("tenant_plugin:before_update", before_update) diff --git a/wafmangeweb/localserver.go b/wafmangeweb/localserver.go index 39a707c..7b6a9ad 100644 --- a/wafmangeweb/localserver.go +++ b/wafmangeweb/localserver.go @@ -71,6 +71,7 @@ func (web *WafWebManager) initRouter(r *gin.Engine) { router.ApiGroupApp.InitAnalysisRouter(RouterGroup) router.ApiGroupApp.InitWafPrivateInfoRouter(RouterGroup) router.ApiGroupApp.InitWafCacheRuleRouter(RouterGroup) + router.ApiGroupApp.InitWafTunnelRouter(RouterGroup) } if global.GWAF_RELEASE == "true" { diff --git a/waftunnelengine/ip_check.go b/waftunnelengine/ip_check.go new file mode 100644 index 0000000..816fe78 --- /dev/null +++ b/waftunnelengine/ip_check.go @@ -0,0 +1,87 @@ +package waftunnelengine + +import ( + "SamWaf/common/zlog" + "SamWaf/model" + "net" + "strings" +) + +// CheckIPAccess 检查IP是否允许访问 +// 返回值: true表示允许访问,false表示拒绝访问 +func CheckIPAccess(clientIP string, tunnel model.Tunnel) bool { + // 如果客户端IP为空,拒绝访问 + if clientIP == "" { + zlog.Warn("客户端IP为空,拒绝访问") + return false + } + + // 处理IP地址,去除端口部分 + if strings.Contains(clientIP, ":") { + host, _, err := net.SplitHostPort(clientIP) + if err != nil { + zlog.Error("解析客户端IP失败: " + err.Error()) + return false + } + clientIP = host + } + + // 检查黑名单(优先级高) + if tunnel.DenyIp != "" { + denyIPs := strings.Split(tunnel.DenyIp, ",") + for _, ip := range denyIPs { + ip = strings.TrimSpace(ip) + if ip == "" { + continue + } + + // 支持CIDR格式和精确匹配 + if strings.Contains(ip, "/") { + // CIDR格式 + _, ipNet, err := net.ParseCIDR(ip) + if err == nil && ipNet.Contains(net.ParseIP(clientIP)) { + zlog.Info("IP " + clientIP + " 在黑名单CIDR " + ip + " 范围内,拒绝访问") + return false + } + } else if ip == clientIP { + // 精确匹配 + zlog.Info("IP " + clientIP + " 在黑名单中,拒绝访问") + return false + } + } + } + + // 检查白名单 + if tunnel.AllowIp != "" { + // 白名单不为空,需要在白名单中才允许访问 + allowIPs := strings.Split(tunnel.AllowIp, ",") + for _, ip := range allowIPs { + ip = strings.TrimSpace(ip) + if ip == "" { + continue + } + + // 支持CIDR格式和精确匹配 + if strings.Contains(ip, "/") { + // CIDR格式 + _, ipNet, err := net.ParseCIDR(ip) + if err == nil && ipNet.Contains(net.ParseIP(clientIP)) { + zlog.Info("IP " + clientIP + " 在白名单CIDR " + ip + " 范围内,允许访问") + return true + } + } else if ip == clientIP { + // 精确匹配 + zlog.Info("IP " + clientIP + " 在白名单中,允许访问") + return true + } + } + + // 如果有白名单但IP不在其中,拒绝访问 + zlog.Info("IP " + clientIP + " 不在白名单中,拒绝访问") + return false + } + + // 如果没有设置白名单,且不在黑名单中,则允许访问 + zlog.Info("隧道访问 IP " + clientIP + " 通过访问控制检查,允许访问") + return true +} diff --git a/waftunnelengine/tcp_handler.go b/waftunnelengine/tcp_handler.go new file mode 100644 index 0000000..68a1212 --- /dev/null +++ b/waftunnelengine/tcp_handler.go @@ -0,0 +1,160 @@ +package waftunnelengine + +import ( + "SamWaf/common/zlog" + "SamWaf/model/waftunnelmodel" + "net" + "strconv" + "time" +) + +// startTCPServer 启动TCP服务器 +func (waf *WafTunnelEngine) startTCPServer(netRuntime waftunnelmodel.NetRunTime) { + addr := ":" + strconv.Itoa(netRuntime.Port) + listener, err := net.Listen("tcp", addr) + if err != nil { + zlog.Error("TCP服务器启动失败,端口: " + strconv.Itoa(netRuntime.Port) + ", 错误: " + err.Error()) + return + } + + key := "tcp" + strconv.Itoa(netRuntime.Port) + // 更新状态 + netClone, _ := waf.NetListerOnline.Get(key) + netClone.Status = 0 + netClone.Svr = listener + waf.NetListerOnline.Set(key, netClone) + + zlog.Info("启动TCP服务器,端口: " + strconv.Itoa(netRuntime.Port)) + + for { + conn, err := listener.Accept() + if err != nil { + zlog.Error("TCP连接接收失败: " + err.Error()) + break + } + + // 获取隧道配置 + tunnelInfo, ok := waf.TunnelTarget.Get("tcp" + strconv.Itoa(netRuntime.Port)) + if !ok { + zlog.Error("未找到端口对应的隧道配置: " + "tcp" + strconv.Itoa(netRuntime.Port)) + conn.Close() + continue + } + + // 检查入站连接数限制 + if tunnelInfo.Tunnel.MaxInConnect > 0 { + inConnCount := waf.TCPConnections.GetPortConnsCountByType(netRuntime.Port, waftunnelmodel.ConnTypeSource) + if inConnCount >= tunnelInfo.Tunnel.MaxInConnect { + zlog.Warn("TCP入站连接数超过限制,当前连接数: " + strconv.Itoa(inConnCount) + + ", 最大限制: " + strconv.Itoa(tunnelInfo.Tunnel.MaxInConnect) + + ", 端口: " + strconv.Itoa(netRuntime.Port)) + conn.Close() + continue + } + } + + // 处理连接 + go waf.handleTCPConnection(conn, netRuntime.Port) + } + + zlog.Info("TCP服务器关闭,端口: " + strconv.Itoa(netRuntime.Port)) +} + +// handleTCPConnection 处理TCP连接 +func (waf *WafTunnelEngine) handleTCPConnection(clientConn net.Conn, port int) { + // 获取客户端IP + clientIP := clientConn.RemoteAddr().String() + + // 获取隧道配置 + tunnelInfo, ok := waf.TunnelTarget.Get("tcp" + strconv.Itoa(port)) + if !ok { + zlog.Error("未找到端口对应的隧道配置: " + "tcp" + strconv.Itoa(port)) + clientConn.Close() + return + } + + // 检查IP访问权限 + if !CheckIPAccess(clientIP, tunnelInfo.Tunnel) { + zlog.Warn("TCP连接被拒绝,客户端IP: " + clientIP + ", 端口: " + strconv.Itoa(port)) + clientConn.Close() + return + } + + // 将客户端连接添加到活动连接列表,标记为来源连接 + waf.TCPConnections.AddConn(port, clientConn, waftunnelmodel.ConnTypeSource) + defer func() { + clientConn.Close() + waf.TCPConnections.RemoveConn(port, clientConn) + }() + + // 检查出站连接数限制 + if tunnelInfo.Tunnel.MaxOutConnect > 0 { + outConnCount := waf.TCPConnections.GetPortConnsCountByType(port, waftunnelmodel.ConnTypeTarget) + if outConnCount >= tunnelInfo.Tunnel.MaxOutConnect { + zlog.Warn("TCP出站连接数超过限制,当前连接数: " + strconv.Itoa(outConnCount) + + ", 最大限制: " + strconv.Itoa(tunnelInfo.Tunnel.MaxOutConnect) + + ", 端口: " + strconv.Itoa(port)) + return + } + } + + // 连接到目标服务器 + targetAddr := tunnelInfo.Tunnel.RemoteIp + ":" + strconv.Itoa(tunnelInfo.Tunnel.RemotePort) + targetConn, err := net.Dial("tcp", targetAddr) + if err != nil { + zlog.Error("连接目标服务器失败: " + targetAddr + ", 错误: " + err.Error()) + return + } + + // 将目标连接也添加到活动连接列表,标记为目标连接 + waf.TCPConnections.AddConn(port, targetConn, waftunnelmodel.ConnTypeTarget) + defer func() { + targetConn.Close() + waf.TCPConnections.RemoveConn(port, targetConn) + }() + + // 设置超时 + if tunnelInfo.Tunnel.ConnTimeout > 0 { + clientConn.SetDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.ConnTimeout) * time.Second)) + targetConn.SetDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.ConnTimeout) * time.Second)) + } + + // 设置读取超时 + if tunnelInfo.Tunnel.ReadTimeout > 0 { + clientConn.SetReadDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.ReadTimeout) * time.Second)) + targetConn.SetReadDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.ReadTimeout) * time.Second)) + } + + // 设置写入超时 + if tunnelInfo.Tunnel.WriteTimeout > 0 { + clientConn.SetWriteDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.WriteTimeout) * time.Second)) + targetConn.SetWriteDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.WriteTimeout) * time.Second)) + } + + // 双向转发数据 + go func() { + buffer := make([]byte, 4096) + for { + n, err := clientConn.Read(buffer) + if err != nil { + return + } + _, err = targetConn.Write(buffer[:n]) + if err != nil { + return + } + } + }() + + buffer := make([]byte, 4096) + for { + n, err := targetConn.Read(buffer) + if err != nil { + return + } + _, err = clientConn.Write(buffer[:n]) + if err != nil { + return + } + } +} diff --git a/waftunnelengine/udp_handler.go b/waftunnelengine/udp_handler.go new file mode 100644 index 0000000..1ecd992 --- /dev/null +++ b/waftunnelengine/udp_handler.go @@ -0,0 +1,160 @@ +package waftunnelengine + +import ( + "SamWaf/common/zlog" + "SamWaf/model/waftunnelmodel" + "net" + "strconv" + "time" +) + +// startUDPServer 启动UDP服务器 +func (waf *WafTunnelEngine) startUDPServer(netRuntime waftunnelmodel.NetRunTime) { + addr := ":" + strconv.Itoa(netRuntime.Port) + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + zlog.Error("UDP地址解析失败,端口: " + strconv.Itoa(netRuntime.Port) + ", 错误: " + err.Error()) + return + } + + conn, err := net.ListenUDP("udp", udpAddr) + if err != nil { + zlog.Error("UDP服务器启动失败,端口: " + strconv.Itoa(netRuntime.Port) + ", 错误: " + err.Error()) + return + } + + // 将服务器连接添加到活动连接列表,标记为来源连接 + waf.UDPConnections.AddConn(netRuntime.Port, conn, waftunnelmodel.ConnTypeSource) + + key := "udp" + strconv.Itoa(netRuntime.Port) + // 更新状态 + netClone, _ := waf.NetListerOnline.Get(key) + netClone.Status = 0 + netClone.Svr = conn + waf.NetListerOnline.Set(key, netClone) + + zlog.Info("启动UDP服务器,端口: " + strconv.Itoa(netRuntime.Port)) + + // 处理UDP数据 + buffer := make([]byte, 1024) + for { + n, remoteAddr, err := conn.ReadFromUDP(buffer) + if err != nil { + zlog.Error("UDP读取失败: " + err.Error()) + break + } + + // 获取隧道配置 + tunnelInfo, ok := waf.TunnelTarget.Get("udp" + strconv.Itoa(netRuntime.Port)) + if !ok { + zlog.Error("未找到端口对应的隧道配置: " + "udp" + strconv.Itoa(netRuntime.Port)) + continue + } + + // 检查入站连接数限制 + if tunnelInfo.Tunnel.MaxInConnect > 0 { + inConnCount := waf.UDPConnections.GetPortConnsCountByType(netRuntime.Port, waftunnelmodel.ConnTypeSource) + if inConnCount >= tunnelInfo.Tunnel.MaxInConnect { + zlog.Warn("UDP入站连接数超过限制,当前连接数: " + strconv.Itoa(inConnCount) + + ", 最大限制: " + strconv.Itoa(tunnelInfo.Tunnel.MaxInConnect) + + ", 端口: " + strconv.Itoa(netRuntime.Port)) + continue + } + } + + // 处理UDP数据 + go waf.handleUDPData(conn, remoteAddr, buffer[:n], netRuntime.Port) + } + + zlog.Info("UDP服务器关闭,端口: " + strconv.Itoa(netRuntime.Port)) + waf.UDPConnections.RemoveConn(netRuntime.Port, conn) +} + +// handleUDPData 处理UDP数据 +func (waf *WafTunnelEngine) handleUDPData(serverConn *net.UDPConn, clientAddr *net.UDPAddr, data []byte, port int) { + // 获取客户端IP + clientIP := clientAddr.String() + + // 获取隧道配置 + tunnelInfo, ok := waf.TunnelTarget.Get("udp" + strconv.Itoa(port)) + if !ok { + zlog.Error("未找到端口对应的隧道配置: " + "udp" + strconv.Itoa(port)) + return + } + + // 检查IP访问权限 + if !CheckIPAccess(clientIP, tunnelInfo.Tunnel) { + zlog.Warn("UDP数据被拒绝,客户端IP: " + clientIP + ", 端口: " + strconv.Itoa(port)) + return + } + + // 检查出站连接数限制 + if tunnelInfo.Tunnel.MaxOutConnect > 0 { + outConnCount := waf.UDPConnections.GetPortConnsCountByType(port, waftunnelmodel.ConnTypeTarget) + if outConnCount >= tunnelInfo.Tunnel.MaxOutConnect { + zlog.Warn("UDP出站连接数超过限制,当前连接数: " + strconv.Itoa(outConnCount) + + ", 最大限制: " + strconv.Itoa(tunnelInfo.Tunnel.MaxOutConnect) + + ", 端口: " + strconv.Itoa(port)) + return + } + } + + // 连接到目标服务器 + targetAddr := tunnelInfo.Tunnel.RemoteIp + ":" + strconv.Itoa(tunnelInfo.Tunnel.RemotePort) + raddr, err := net.ResolveUDPAddr("udp", targetAddr) + if err != nil { + zlog.Error("解析目标地址失败: " + targetAddr + ", 错误: " + err.Error()) + return + } + + // 创建到目标的连接 + targetConn, err := net.DialUDP("udp", nil, raddr) + if err != nil { + zlog.Error("连接目标服务器失败: " + targetAddr + ", 错误: " + err.Error()) + return + } + + // 将目标连接添加到活动连接列表,标记为目标连接 + waf.UDPConnections.AddConn(port, targetConn, waftunnelmodel.ConnTypeTarget) + defer func() { + targetConn.Close() + waf.UDPConnections.RemoveConn(port, targetConn) + }() + + // 设置超时 + if tunnelInfo.Tunnel.ConnTimeout > 0 { + targetConn.SetDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.ConnTimeout) * time.Second)) + } + + // 设置读取超时 + if tunnelInfo.Tunnel.ReadTimeout > 0 { + targetConn.SetReadDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.ReadTimeout) * time.Second)) + } + + // 设置写入超时 + if tunnelInfo.Tunnel.WriteTimeout > 0 { + targetConn.SetWriteDeadline(time.Now().Add(time.Duration(tunnelInfo.Tunnel.WriteTimeout) * time.Second)) + } + + // 发送数据到目标 + _, err = targetConn.Write(data) + if err != nil { + zlog.Error("发送数据到目标失败: " + err.Error()) + return + } + + // 接收目标响应 + buffer := make([]byte, 4096) + n, _, err := targetConn.ReadFromUDP(buffer) + if err != nil { + zlog.Error("从目标接收数据失败: " + err.Error()) + return + } + + // 发送响应回客户端 + _, err = serverConn.WriteToUDP(buffer[:n], clientAddr) + if err != nil { + zlog.Error("发送响应到客户端失败: " + err.Error()) + return + } +} diff --git a/waftunnelengine/waf_tunnel_engine.go b/waftunnelengine/waf_tunnel_engine.go new file mode 100644 index 0000000..d6f8b1b --- /dev/null +++ b/waftunnelengine/waf_tunnel_engine.go @@ -0,0 +1,354 @@ +package waftunnelengine + +import ( + "SamWaf/common/zlog" + "SamWaf/global" + "SamWaf/model" + "SamWaf/model/waftunnelmodel" + "net" + "strconv" + "strings" +) + +type WafTunnelEngine struct { + //隧道情况(key:协议+端口 : tunnelSafe信息里面详细等) + TunnelTarget *waftunnelmodel.SafeTunnelMap + //服务在线情况(key:协议+端口,value :连接接入控制器) + NetListerOnline *waftunnelmodel.SafeNetMap + //TCP连接管理 + TCPConnections *waftunnelmodel.SafeTCPConnMap + //UDP连接管理 + UDPConnections *waftunnelmodel.SafeUDPConnMap +} + +func NewWafTunnelEngine() *WafTunnelEngine { + return &WafTunnelEngine{ + TunnelTarget: waftunnelmodel.NewSafeTunnelMap(), + NetListerOnline: waftunnelmodel.NewSafeNetMap(), + TCPConnections: waftunnelmodel.NewSafeTCPConnMap(), + UDPConnections: waftunnelmodel.NewSafeUDPConnMap(), + } +} + +// StartTunnel 启动tunnel +func (waf *WafTunnelEngine) StartTunnel() { + // 启动tunnel + waf.LoadAllTunnel() + waf.StartAllTunnelServer() +} + +// CloseTunnel 关闭tunnel +func (waf *WafTunnelEngine) CloseTunnel() { + // 关闭tunnel + zlog.Info("开始关闭所有隧道服务...") + waf.StopAllTunnelServer() + // 清理隧道目标信息 + waf.TunnelTarget.Clear() + + // 清理服务在线情况 + waf.NetListerOnline.Clear() + zlog.Info("所有隧道服务已关闭") +} + +// LoadAllTunnel 加载全部tunnel +func (waf *WafTunnelEngine) LoadAllTunnel() { + //重新查询 + var tunnels []model.Tunnel + global.GWAF_LOCAL_DB.Find(&tunnels) + for i := 0; i < len(tunnels); i++ { + waf.LoadTunnel(tunnels[i]) + } +} + +// LoadTunnel 加载指定tunnel +func (waf *WafTunnelEngine) LoadTunnel(inTunnel model.Tunnel) []waftunnelmodel.NetRunTime { + + netRunTimes := make([]waftunnelmodel.NetRunTime, 0) + + // 先处理端口 + portStr := inTunnel.Port + portStrArray := strings.Split(portStr, ",") + portArray := make([]int, 0, len(portStrArray)) + for _, portItem := range portStrArray { + portItem = strings.TrimSpace(portItem) + if portItem == "" { + continue + } + port, err := strconv.Atoi(portItem) + if err != nil { + continue + } + portArray = append(portArray, port) + + key := inTunnel.Protocol + portItem //唯一识别 协议+端口 + _, ok := waf.NetListerOnline.Get(key) + if ok == false { + //不存在,创建一个 + netRuntime := waftunnelmodel.NetRunTime{ + ServerType: inTunnel.Protocol, + Port: port, + Status: inTunnel.StartStatus, + Svr: nil, + } + waf.NetListerOnline.Set(key, netRuntime) + netRunTimes = append(netRunTimes, netRuntime) + } + //设置或者重新更新引用隧道的基本信息 + waf.TunnelTarget.Set(key, &waftunnelmodel.TunnelSafe{ + Tunnel: inTunnel, + }) + } + + return netRunTimes +} + +// EditTunnel 编辑隧道配置 +func (waf *WafTunnelEngine) EditTunnel(oldTunnel model.Tunnel, newTunnel model.Tunnel) []waftunnelmodel.NetRunTime { + // 返回值:新增的端口运行时, 移除的端口运行时 + addedRunTimes := make([]waftunnelmodel.NetRunTime, 0) + + // 解析旧端口列表 + oldPortMap := make(map[string]bool) + oldPortStrArray := strings.Split(oldTunnel.Port, ",") + for _, portItem := range oldPortStrArray { + portItem = strings.TrimSpace(portItem) + if portItem == "" { + continue + } + oldPortMap[portItem] = true + } + + // 解析新端口列表 + newPortMap := make(map[string]bool) + newPortStrArray := strings.Split(newTunnel.Port, ",") + for _, portItem := range newPortStrArray { + portItem = strings.TrimSpace(portItem) + if portItem == "" { + continue + } + newPortMap[portItem] = true + } + + // 情况1:端口完全没变,只修改了其他信息 + if len(oldPortMap) == len(newPortMap) { + allSame := true + for port := range oldPortMap { + if !newPortMap[port] { + allSame = false + break + } + } + + //如果是端口相同,也存在是否存在切换状态 + if allSame { + // 端口完全相同,只需要更新隧道信息 + for port := range oldPortMap { + key := oldTunnel.Protocol + port + // 更新隧道目标信息 + if tunnelSafe, ok := waf.TunnelTarget.Get(key); ok { + tunnelSafe.Tunnel = newTunnel + waf.TunnelTarget.Set(key, tunnelSafe) + } + } + if oldTunnel.StartStatus != newTunnel.StartStatus { + if newTunnel.StartStatus == 0 { + waf.RemoveTunnel(newTunnel) + } else { + netRunTimes := waf.LoadTunnel(newTunnel) + addedRunTimes = append(addedRunTimes, netRunTimes...) + } + } + return addedRunTimes + } + } + + // 情况2和3:端口有变化 + + // 处理减少的端口 - 需要移除 + for port := range oldPortMap { + if !newPortMap[port] { + // 这个端口在新配置中不存在,需要移除 + key := oldTunnel.Protocol + port + if netRuntime, ok := waf.NetListerOnline.Get(key); ok { + // 停止服务 + waf.StopTunnelServer(netRuntime) + // 从在线列表中移除 + waf.NetListerOnline.Delete(key) + // 从隧道目标中移除 + waf.TunnelTarget.Delete(key) + zlog.Info("已移除隧道服务: " + oldTunnel.Protocol + " 端口: " + port) + } + } + } + + // 处理新增的端口 - 需要添加 + for port := range newPortMap { + if !oldPortMap[port] { + // 这个端口在旧配置中不存在,需要添加 + portInt, err := strconv.Atoi(port) + if err != nil { + continue + } + + key := newTunnel.Protocol + port + _, ok := waf.NetListerOnline.Get(key) + if !ok { + // 不存在,创建一个 + netRuntime := waftunnelmodel.NetRunTime{ + ServerType: newTunnel.Protocol, + Port: portInt, + Status: newTunnel.StartStatus, + Svr: nil, + } + waf.NetListerOnline.Set(key, netRuntime) + addedRunTimes = append(addedRunTimes, netRuntime) + } + // 设置或更新隧道基本信息 + waf.TunnelTarget.Set(key, &waftunnelmodel.TunnelSafe{ + Tunnel: newTunnel, + }) + } else { + // 端口相同但其他信息可能变了,更新隧道信息 + key := newTunnel.Protocol + port + if tunnelSafe, ok := waf.TunnelTarget.Get(key); ok { + tunnelSafe.Tunnel = newTunnel + waf.TunnelTarget.Set(key, tunnelSafe) + } + } + } + + return addedRunTimes +} + +// RemoveTunnel 移除指定tunnel +func (waf *WafTunnelEngine) RemoveTunnel(inTunnel model.Tunnel) []waftunnelmodel.NetRunTime { + removedRunTimes := make([]waftunnelmodel.NetRunTime, 0) + + // 处理端口 + portStr := inTunnel.Port + portStrArray := strings.Split(portStr, ",") + for _, portItem := range portStrArray { + portItem = strings.TrimSpace(portItem) + if portItem == "" { + continue + } + _, err := strconv.Atoi(portItem) + if err != nil { + continue + } + + key := inTunnel.Protocol + portItem // 唯一识别 协议+端口 + netRuntime, ok := waf.NetListerOnline.Get(key) + if ok { + // 存在,需要移除 + removedRunTimes = append(removedRunTimes, netRuntime) + // 停止服务 + waf.StopTunnelServer(netRuntime) + // 从在线列表中移除 + waf.NetListerOnline.Delete(key) + // 从隧道目标中移除 + waf.TunnelTarget.Delete(key) + zlog.Info("已移除隧道服务: " + inTunnel.Protocol + " 端口: " + portItem) + } + } + + return removedRunTimes +} + +// StartAllTunnelServer 开启所有隧道服务 +func (waf *WafTunnelEngine) StartAllTunnelServer() { + netMap := waf.NetListerOnline.GetAll() + for _, v := range netMap { + waf.StartTunnelServer(v) + } + waf.EnumAllPortTunnelServer() +} + +// EnumAllPortTunnelServer 罗列所有隧道端口 +func (waf *WafTunnelEngine) EnumAllPortTunnelServer() { + onlinePorts := "" + netMap := waf.NetListerOnline.GetAll() + for _, v := range netMap { + onlinePorts = strconv.Itoa(v.Port) + "," + onlinePorts + } + // 可以将端口信息存储到全局变量中 + global.GWAF_RUNTIME_CURRENT_TUNNELPORT = onlinePorts +} + +// StartTunnelServer 启动指定隧道服务 +func (waf *WafTunnelEngine) StartTunnelServer(netRuntime waftunnelmodel.NetRunTime) { + if netRuntime.Status == 0 { + // 已启动完成的就不处理 + return + } + if netRuntime.ServerType == "" { + // 如果协议类型为空就不处理 + return + } + + go func(netRuntime waftunnelmodel.NetRunTime) { + defer func() { + e := recover() + if e != nil { + zlog.Warn("tunnel server recover ", e) + } + }() + + // 根据协议类型启动不同的服务 + switch strings.ToLower(netRuntime.ServerType) { + case "tcp": + waf.startTCPServer(netRuntime) + case "udp": + waf.startUDPServer(netRuntime) + default: + zlog.Warn("不支持的协议类型: " + netRuntime.ServerType) + } + }(netRuntime) +} + +// StopAllTunnelServer 关闭所有隧道服务 +func (waf *WafTunnelEngine) StopAllTunnelServer() { + netMap := waf.NetListerOnline.GetAll() + for _, v := range netMap { + waf.StopTunnelServer(v) + } +} + +// StopTunnelServer 关闭指定隧道服务 +func (waf *WafTunnelEngine) StopTunnelServer(netRuntime waftunnelmodel.NetRunTime) { + portStr := strconv.Itoa(netRuntime.Port) + + // 关闭服务器 + if netRuntime.Svr != nil { + // 根据不同类型关闭服务 + switch svr := netRuntime.Svr.(type) { + case net.Listener: + svr.Close() + case *net.UDPConn: + svr.Close() + } + } + + // 获取详细的连接统计 + tcpSourceCount := waf.TCPConnections.GetPortConnsCountByType(netRuntime.Port, waftunnelmodel.ConnTypeSource) + tcpTargetCount := waf.TCPConnections.GetPortConnsCountByType(netRuntime.Port, waftunnelmodel.ConnTypeTarget) + udpSourceCount := waf.UDPConnections.GetPortConnsCountByType(netRuntime.Port, waftunnelmodel.ConnTypeSource) + udpTargetCount := waf.UDPConnections.GetPortConnsCountByType(netRuntime.Port, waftunnelmodel.ConnTypeTarget) + + // 总连接数 + tcpCount := tcpSourceCount + tcpTargetCount + udpCount := udpSourceCount + udpTargetCount + + if tcpCount > 0 || udpCount > 0 { + zlog.Info("正在关闭端口 " + portStr + " 的连接: " + + "TCP总计=" + strconv.Itoa(tcpCount) + + "(来源=" + strconv.Itoa(tcpSourceCount) + + ",目标=" + strconv.Itoa(tcpTargetCount) + "), " + + "UDP总计=" + strconv.Itoa(udpCount) + + "(来源=" + strconv.Itoa(udpSourceCount) + + ",目标=" + strconv.Itoa(udpTargetCount) + ")") + + waf.TCPConnections.ClosePortConns(netRuntime.Port) + waf.UDPConnections.ClosePortConns(netRuntime.Port) + } +}