feat: add tunnel protection

#IATZU4
This commit is contained in:
samwaf
2025-05-22 16:03:12 +08:00
parent 060992e7d5
commit 9639a64317
27 changed files with 1886 additions and 17 deletions
+2
View File
@@ -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
)
+260
View File
@@ -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)
}
}
+3 -4
View File
@@ -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)
}
+6
View File
@@ -0,0 +1,6 @@
package enums
const (
//隧道
ChanComTypeTunnel = iota
)
+7
View File
@@ -0,0 +1,7 @@
package enums
const (
OP_TYPE_NEW = iota
OP_TYPE_UPDATE
OP_TYPE_DELETE
)
+2
View File
@@ -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) //证书处理链
+5 -3
View File
@@ -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 // 任务计划
)
+40
View File
@@ -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)
+54
View File
@@ -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"`
}
+8
View File
@@ -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{} //修改前的内容
}
+25
View File
@@ -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"` //备注
}
+313
View File
@@ -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
}
+143
View File
@@ -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)
}
+9
View File
@@ -0,0 +1,9 @@
package waftunnelmodel
type NetRunTime struct {
//tcp udp
ServerType string
Port int
Status int // 0 是启动完成 ,1 是新增,2 是编辑 3,是删除
Svr interface{} // 支持任何网络连接类型
}
+12
View File
@@ -0,0 +1,12 @@
package waftunnelmodel
import (
"SamWaf/model"
"sync"
)
// TunnelSafe 隧道安全配置
type TunnelSafe struct {
Mux sync.Mutex //互斥锁
Tunnel model.Tunnel
}
+1
View File
@@ -37,6 +37,7 @@ type ApiGroup struct {
AnalysisRouter
WafPrivateInfoRouter
WafCacheRuleRouter
WafTunnelRouter
}
type PublicApiGroup struct {
LoginRouter
+20
View File
@@ -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)
}
+1
View File
@@ -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)})
+171
View File
@@ -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
}
-10
View File
@@ -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段内
+39
View File
@@ -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
}
+3
View File
@@ -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)
+1
View File
@@ -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" {
+87
View File
@@ -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
}
+160
View File
@@ -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
}
}
}
+160
View File
@@ -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
}
}
+354
View File
@@ -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)
}
}