diff --git a/features/firewall/api_firewall.go b/features/firewall/api_firewall.go new file mode 100644 index 00000000..136f3b6a --- /dev/null +++ b/features/firewall/api_firewall.go @@ -0,0 +1,88 @@ +package firewall + +import ( + "net/http" + + "ems.agt/features/firewall/model" + "ems.agt/features/firewall/service" + "ems.agt/lib/core/utils/ctx" + "ems.agt/lib/core/vo/result" + "ems.agt/lib/services" + "ems.agt/restagent/config" +) + +// 防火墙管理接口添加到路由 +func Routers() []services.RouterItem { + // 实例化控制层 FirewallApi 结构体 + var apis = &FirewallApi{ + firewallService: *service.NewServiceFirewall, + } + + rs := [...]services.RouterItem{ + { + Method: "GET", + Pattern: "/base", + Handler: apis.BaseInfo, + Middleware: nil, //midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/rule", + Handler: apis.Rule, + Middleware: nil, //midware.Authorize(nil), + }, + // 添加更多的 Router 对象... + } + + // 生成两组前缀路由 + rsPrefix := []services.RouterItem{} + for _, v := range rs { + path := "/firewallManage/{apiVersion}" + v.Pattern + // 固定前缀 + v.Pattern = config.DefaultUriPrefix + path + rsPrefix = append(rsPrefix, v) + // 可配置 + v.Pattern = config.UriPrefix + path + rsPrefix = append(rsPrefix, v) + } + return rsPrefix +} + +// 防火墙管理 +// +// PATH /firewallManage +type FirewallApi struct { + firewallService service.ServiceFirewall +} + +// 获取防火墙基础信息 +// +// GET /base +func (s *FirewallApi) BaseInfo(w http.ResponseWriter, r *http.Request) { + data, err := s.firewallService.LoadBaseInfo() + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + + ctx.JSON(w, 200, result.OkData(data)) +} + +// 获取防火墙规则列表分页 +// +// GET /rule +func (s *FirewallApi) Rule(w http.ResponseWriter, r *http.Request) { + var body model.RuleQuerys + err := ctx.ShouldBindJSON(r, &body) + if err != nil || body.Type == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + data, err := s.firewallService.RulePage(body) + if err != nil { + ctx.JSON(w, 400, result.ErrMsg(err.Error())) + return + } + + ctx.JSON(w, 200, result.OkData(data)) +} diff --git a/features/firewall/model/firewall.go b/features/firewall/model/firewall.go new file mode 100644 index 00000000..782aa89f --- /dev/null +++ b/features/firewall/model/firewall.go @@ -0,0 +1,13 @@ +package model + +type Firewall struct { + ID int64 `json:"id" xorm:"id"` + CreatedAt int64 `json:"createdAt" xorm:"created_at"` + UpdatedAt int64 `json:"updatedAt" xorm:"updated_at"` + Type string `json:"type" xorm:"type"` + Port string `json:"port" xorm:"port"` + Protocol string `json:"protocol" xorm:"protocol"` + Address string `json:"address" xorm:"address"` + Strategy string `json:"strategy" xorm:"strategy"` + Description string `json:"description" xorm:"description"` +} diff --git a/features/firewall/model/firewall_vo.go b/features/firewall/model/firewall_vo.go new file mode 100644 index 00000000..d4e35531 --- /dev/null +++ b/features/firewall/model/firewall_vo.go @@ -0,0 +1,64 @@ +package model + +type FirewallBaseInfo struct { + Name string `json:"name"` + Status string `json:"status"` + Version string `json:"version"` + PingStatus string `json:"pingStatus"` +} + +type RuleQuerys struct { + PageNum int `json:"pageNum" validate:"required,number"` + PageSize int `json:"pageSize" validate:"required,number"` + Info string `json:"info"` + Status string `json:"status"` + Strategy string `json:"strategy"` + Type string `json:"type" validate:"required"` +} + +type FirewallOperation struct { + Operation string `json:"operation" validate:"required,oneof=start stop disablePing enablePing"` +} + +type PortRuleOperate struct { + Operation string `json:"operation" validate:"required,oneof=add remove"` + Address string `json:"address"` + Port string `json:"port" validate:"required"` + Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type UpdateFirewallDescription struct { + Type string `json:"type"` + Address string `json:"address"` + Port string `json:"port"` + Protocol string `json:"protocol"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type AddrRuleOperate struct { + Operation string `json:"operation" validate:"required,oneof=add remove"` + Address string `json:"address" validate:"required"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type PortRuleUpdate struct { + OldRule PortRuleOperate `json:"oldRule"` + NewRule PortRuleOperate `json:"newRule"` +} + +type AddrRuleUpdate struct { + OldRule AddrRuleOperate `json:"oldRule"` + NewRule AddrRuleOperate `json:"newRule"` +} + +type BatchRuleOperate struct { + Type string `json:"type" validate:"required"` + Rules []PortRuleOperate `json:"rules"` +} diff --git a/features/firewall/repo/repo_firewall.go b/features/firewall/repo/repo_firewall.go new file mode 100644 index 00000000..e95a0564 --- /dev/null +++ b/features/firewall/repo/repo_firewall.go @@ -0,0 +1,133 @@ +package repo + +import ( + "strings" + + "ems.agt/features/firewall/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/log" +) + +// 实例化数据层 RepoFirewall 结构体 +var NewRepoFirewall = &RepoFirewall{ + selectSql: `select + id, created_at, updated_at, type, port, protocol, address, strategy, description + from monitor_firewall`, + + resultMap: map[string]string{ + "id": "ID", + "created_at": "CreatedAt", + "updated_at": "UpdatedAt", + "type": "Type", + "port": "Port", + "protocol": "Protocol", + "address": "Address", + "strategy": "Strategy", + "description": "Description", + }, +} + +// RepoFirewall 防火墙 数据层处理 +type RepoFirewall struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *RepoFirewall) convertResultRows(rows []map[string]any) []model.Firewall { + arr := make([]model.Firewall, 0) + for _, row := range rows { + UdmUser := model.Firewall{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + datasource.SetFieldValue(&UdmUser, keyMapper, value) + } + } + arr = append(arr, UdmUser) + } + return arr +} + +// List 根据实体查询 +func (r *RepoFirewall) List(f model.Firewall) []model.Firewall { + // 查询条件拼接 + var conditions []string + var params []any + if f.Type != "" { + conditions = append(conditions, "type = ?") + params = append(params, f.Type) + } + if f.Protocol != "" { + conditions = append(conditions, "protocol = ?") + params = append(params, f.Protocol) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + log.Errorf("query err => %v", err) + } + + // 转换实体 + return r.convertResultRows(results) +} + +// Insert 新增实体 +func (r *RepoFirewall) Insert(f model.Firewall) int64 { + results, err := datasource.DefaultDB().Table("monitor_firewall").Insert(f) + if err != nil { + return results + } + return results +} + +// Update 修改更新 +func (r *RepoFirewall) Update(f model.Firewall) int64 { + // 查询先 + var fd model.Firewall + if f.Type == "port" { + has, err := datasource.DefaultDB().Table("monitor_firewall").Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", "port", f.Port, f.Protocol, f.Address, f.Strategy).Get(&fd) + if !has || err != nil { + return 0 + } + } else { + has, err := datasource.DefaultDB().Table("monitor_firewall").Where("type = ? AND address = ? AND strategy = ?", "address", f.Address, f.Strategy).Get(&fd) + if !has || err != nil { + return 0 + } + } + f.ID = fd.ID + + results, err := datasource.DefaultDB().Table("monitor_firewall").Where("id = ?", f.ID).Update(f) + if err != nil { + return 0 + } + return results +} + +// Delete 删除实体 +func (r *RepoFirewall) Delete(id int64) int64 { + results, err := datasource.DefaultDB().Table("u_sub_user").Where("id = ?", id).Delete() + if err != nil { + return results + } + return results +} + +// DeleteFirewallRecord 删除实体 +func (r *RepoFirewall) DeleteFirewallRecord(fType, port, protocol, address, strategy string) int64 { + results, err := datasource.DefaultDB().Table("u_sub_user").Where("type = ? AND port = ? AND protocol = ? AND address = ? AND strategy = ?", fType, port, protocol, address, strategy).Delete() + if err != nil { + return results + } + return results +} diff --git a/features/firewall/service/service_firewall.go b/features/firewall/service/service_firewall.go new file mode 100644 index 00000000..62579a25 --- /dev/null +++ b/features/firewall/service/service_firewall.go @@ -0,0 +1,207 @@ +package service + +import ( + "fmt" + "os" + "strconv" + "strings" + + "ems.agt/features/firewall/model" + "ems.agt/features/firewall/repo" + "ems.agt/lib/core/cmd" + "ems.agt/lib/core/utils/firewall" + fireClient "ems.agt/lib/core/utils/firewall/client" + "ems.agt/lib/core/utils/scan" +) + +// 实例化服务层 ServiceFirewall 结构体 +var NewServiceFirewall = &ServiceFirewall{ + repoFirewall: *repo.NewRepoFirewall, +} + +// ServiceFirewall 防火墙 服务层处理 +type ServiceFirewall struct { + repoFirewall repo.RepoFirewall +} + +// LoadBaseInfo 获取防火墙基础信息 +func (s *ServiceFirewall) LoadBaseInfo() (model.FirewallBaseInfo, error) { + var baseInfo model.FirewallBaseInfo + baseInfo.PingStatus = s.pingStatus() + baseInfo.Status = "not running" + baseInfo.Version = "-" + baseInfo.Name = "-" + client, err := firewall.NewFirewallClient() + if err != nil { + if err.Error() == "no such type" { + return baseInfo, nil + } + return baseInfo, err + } + baseInfo.Name = client.Name() + baseInfo.Status, err = client.Status() + if err != nil { + return baseInfo, err + } + if baseInfo.Status == "not running" { + return baseInfo, err + } + baseInfo.Version, err = client.Version() + if err != nil { + return baseInfo, err + } + return baseInfo, nil +} + +// LoadBaseInfo 获取防火墙基础信息 +func (s *ServiceFirewall) RulePage(querys model.RuleQuerys) (map[string]any, error) { + var ( + datas []fireClient.FireInfo + backDatas []fireClient.FireInfo + ) + + data := map[string]any{ + "total": 0, + "rows": backDatas, + } + + client, err := firewall.NewFirewallClient() + if err != nil { + return data, err + } + if querys.Type == "port" { + ports, err := client.ListPort() + if err != nil { + return data, err + } + if len(querys.Info) != 0 { + for _, port := range ports { + if strings.Contains(port.Port, querys.Info) { + datas = append(datas, port) + } + } + } else { + datas = ports + } + } else { + addrs, err := client.ListAddress() + if err != nil { + return data, err + } + if len(querys.Info) != 0 { + for _, addr := range addrs { + if strings.Contains(addr.Address, querys.Info) { + datas = append(datas, addr) + } + } + } else { + datas = addrs + } + } + + var datasFilterStatus []fireClient.FireInfo + if len(querys.Status) != 0 { + for _, data := range datas { + portItem, _ := strconv.Atoi(data.Port) + if querys.Status == "free" && !scan.ScanPortWithProto(portItem, data.Protocol) { + datasFilterStatus = append(datasFilterStatus, data) + } + if querys.Status == "used" && scan.ScanPortWithProto(portItem, data.Protocol) { + datasFilterStatus = append(datasFilterStatus, data) + } + } + } else { + datasFilterStatus = datas + } + var datasFilterStrategy []fireClient.FireInfo + if len(querys.Strategy) != 0 { + for _, data := range datasFilterStatus { + if querys.Strategy == data.Strategy { + datasFilterStrategy = append(datasFilterStrategy, data) + } + } + } else { + datasFilterStrategy = datasFilterStatus + } + + total, start, end := len(datasFilterStrategy), (querys.PageNum-1)*querys.PageSize, querys.PageNum*querys.PageSize + if start > total { + backDatas = make([]fireClient.FireInfo, 0) + } else { + if end >= total { + end = total + } + backDatas = datasFilterStrategy[start:end] + } + + datasFromDB := s.repoFirewall.List(model.Firewall{}) + for i := 0; i < len(backDatas); i++ { + for _, des := range datasFromDB { + if querys.Type != des.Type { + continue + } + if backDatas[i].Port == des.Port && querys.Type == "port" && + backDatas[i].Protocol == des.Protocol && + backDatas[i].Strategy == des.Strategy && + backDatas[i].Address == des.Address { + backDatas[i].Description = des.Description + break + } + if querys.Type == "address" && backDatas[i].Strategy == des.Strategy && backDatas[i].Address == des.Address { + backDatas[i].Description = des.Description + break + } + } + } + + if querys.Type == "port" { + for i := 0; i < len(backDatas); i++ { + port, _ := strconv.Atoi(backDatas[i].Port) + backDatas[i].IsUsed = scan.ScanPort(port) + if backDatas[i].Protocol == "udp" { + backDatas[i].IsUsed = scan.ScanUDPPort(port) + continue + } + } + } + go s.cleanUnUsedData(client) + + return data, nil +} + +func (s *ServiceFirewall) pingStatus() string { + if _, err := os.Stat("/etc/sysctl.conf"); err != nil { + return "None" + } + sudo := cmd.SudoHandleCmd() + command := fmt.Sprintf("%s cat /etc/sysctl.conf | grep net/ipv4/icmp_echo_ignore_all= ", sudo) + stdout, _ := cmd.Exec(command) + if stdout == "net/ipv4/icmp_echo_ignore_all=1\n" { + return "Enable" + } + return "Disable" +} + +func (s *ServiceFirewall) cleanUnUsedData(client firewall.FirewallClient) { + list, _ := client.ListPort() + addressList, _ := client.ListAddress() + list = append(list, addressList...) + if len(list) == 0 { + return + } + records := s.repoFirewall.List(model.Firewall{}) + if len(records) == 0 { + return + } + for _, item := range list { + for i := 0; i < len(records); i++ { + if records[i].Port == item.Port && records[i].Protocol == item.Protocol && records[i].Strategy == item.Strategy && records[i].Address == item.Address { + records = append(records[:i], records[i+1:]...) + } + } + } + + for _, record := range records { + _ = s.repoFirewall.Delete(record.ID) + } +} diff --git a/features/udm_user/api_udm_user.go b/features/udm_user/api_udm_user.go index 4a70b0de..c8af06c1 100644 --- a/features/udm_user/api_udm_user.go +++ b/features/udm_user/api_udm_user.go @@ -187,6 +187,9 @@ func NeInfoByUDM(neId string) (*dborm.NeInfo, error) { log.Error("dborm.XormGetNeInfo is failed:", err) return nil, err } + if neInfo == nil || neInfo.Ip == "" { + return nil, fmt.Errorf("not ne_info or not IP") + } return neInfo, nil } @@ -203,6 +206,7 @@ type UdmUserApi struct { // GET /auths func (s *UdmUserApi) UdmAuthUserList(w http.ResponseWriter, r *http.Request) { querys := ctx.QueryMap(r) + querys["neId"] = "-" data := s.authUser.Page(querys) ctx.JSON(w, 200, result.Ok(data)) } @@ -217,6 +221,7 @@ func (s *UdmUserApi) UdmAuthUserSave(w http.ResponseWriter, r *http.Request) { return } + neId = "-" data := s.authUser.Save(neId) ctx.JSON(w, 200, result.OkData(data)) } @@ -246,7 +251,7 @@ func (s *UdmUserApi) UdmAuthUserInfo(w http.ResponseWriter, r *http.Request) { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return } - + neId = "-" var userInfo model.UdmAuthUser list := s.authUser.List(model.UdmAuthUser{NeID: neId, Imsi: imsi}) if len(list) > 0 { @@ -300,7 +305,8 @@ func (s *UdmUserApi) UdmAuthUserAdd(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.authUser.Insert(neInfo.NeId, body) + neId = "-" + s.authUser.Insert(neId, body) } ctx.JSON(w, 200, result.OkData(data)) } @@ -340,7 +346,8 @@ func (s *UdmUserApi) UdmAuthUserAdds(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.authUser.Inserts(neInfo.NeId, body, num) + neId = "-" + s.authUser.Inserts(neId, body, num) } ctx.JSON(w, 200, result.OkData(data)) } @@ -392,7 +399,8 @@ func (s *UdmUserApi) UdmAuthUserEdit(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.authUser.Update(neInfo.NeId, body) + neId = "-" + s.authUser.Update(neId, body) } ctx.JSON(w, 200, result.OkData(data)) } @@ -425,6 +433,7 @@ func (s *UdmUserApi) UdmAuthUserRemove(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { + neId = "-" s.authUser.Delete(neId, imsi) } ctx.JSON(w, 200, result.OkData(data)) @@ -459,6 +468,7 @@ func (s *UdmUserApi) UdmAuthUserRemoves(w http.ResponseWriter, r *http.Request) // 命令ok时 if strings.Contains(data, "ok") { + neId = "-" s.authUser.Deletes(neId, imsi, num) } ctx.JSON(w, 200, result.OkData(data)) @@ -473,15 +483,16 @@ func (s *UdmUserApi) UdmAuthUserExport(w http.ResponseWriter, r *http.Request) { ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) return } + neId = "-" list := s.authUser.List(model.UdmAuthUser{NeID: neId}) // 文件名 fileName := fmt.Sprintf("OMC_AUTH_USER_EXPORT_%s_%d.csv", neId, time.Now().UnixMilli()) filePath := fmt.Sprintf("%s/upload/mml/%s", conf.Get("ne.omcdir"), fileName) // 转换数据 data := [][]string{} - data = append(data, []string{"imsi", "ki", "amf", "algo", "opc", "status"}) + data = append(data, []string{"imsi", "ki", "amf", "algo", "opc"}) for _, v := range list { - data = append(data, []string{v.Imsi, v.Ki, v.Amf, v.AlgoIndex, v.Opc, v.Status}) + data = append(data, []string{v.Imsi, v.Ki, v.Amf, v.AlgoIndex, v.Opc}) } // 输出到文件 err := file.WriterCSVFile(data, filePath) @@ -502,6 +513,17 @@ func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { return } + // 获取文件名 + _, fileHeader, err := r.FormFile("file") + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + if !strings.HasSuffix(fileHeader.Filename, ".csv") { + ctx.JSON(w, 200, result.ErrMsg("请上传CSV格式文件,内容字段imsi, ki, algo, amf, opc")) + return + } + neInfo, err := NeInfoByUDM(neId) if err != nil { ctx.JSON(w, 200, result.ErrMsg(err.Error())) @@ -509,7 +531,7 @@ func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { } // 文件名 - fileName := fmt.Sprintf("OMC_AUTH_USER_IMPORT_%s_%d.csv", neId, time.Now().UnixMilli()) + fileName := fmt.Sprintf("OMC_AUTH_USER_IMPORT_%s_%d_%s", neId, time.Now().UnixMilli(), fileHeader.Filename) filePath := fmt.Sprintf("%s/upload/mml/%s", conf.Get("ne.omcdir"), fileName) dstPath := conf.Get("mml.upload").(string) // 输出保存文件 @@ -526,7 +548,7 @@ func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { return } - msg := fmt.Sprintf("import authdat:path=%s", fmt.Sprintf("%s%s", dstPath, fileName)) + msg := fmt.Sprintf("import authdat:path=%s", fmt.Sprintf("%s/%s", dstPath, fileName)) // 发送MML data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg) @@ -537,10 +559,10 @@ func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { data := file.ReadCSVFile(filePath) + neId = "-" s.authUser.InsertCSV(neId, data) } - // ctx.JSON(w, 200, result.OkData(data)) - ctx.FileAttachment(w, r, filePath, fileName) + ctx.JSON(w, 200, result.OkData(data)) } // UDM签约用户 @@ -548,6 +570,7 @@ func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { // GET /subs func (s *UdmUserApi) UdmSubUserList(w http.ResponseWriter, r *http.Request) { querys := ctx.QueryMap(r) + querys["neId"] = "-" data := s.subUser.Page(querys) ctx.JSON(w, 200, result.Ok(data)) } @@ -562,6 +585,7 @@ func (s *UdmUserApi) UdmSubUserSave(w http.ResponseWriter, r *http.Request) { return } + neId = "-" data := s.subUser.Save(neId) ctx.JSON(w, 200, result.OkData(data)) } @@ -592,6 +616,7 @@ func (s *UdmUserApi) UdmSubUserInfo(w http.ResponseWriter, r *http.Request) { return } + neId = "-" var userInfo model.UdmSubUser list := s.subUser.List(model.UdmSubUser{NeID: neId, Imsi: imsi}) if len(list) > 0 { @@ -668,7 +693,8 @@ func (s *UdmUserApi) UdmSubUserAdd(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.subUser.Insert(neInfo.NeId, body) + neId = "-" + s.subUser.Insert(neId, body) } ctx.JSON(w, 200, result.OkData(data)) } @@ -713,7 +739,8 @@ func (s *UdmUserApi) UdmSubUserAdds(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.subUser.Inserts(neInfo.NeId, body, num) + neId = "-" + s.subUser.Inserts(neId, body, num) } ctx.JSON(w, 200, result.OkData(data)) } @@ -752,7 +779,8 @@ func (s *UdmUserApi) UdmSubUserAdd4G(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.subUser.Insert4G(neInfo.NeId, body) + neId = "-" + s.subUser.Insert4G(neId, body) } ctx.JSON(w, 200, result.OkData(data)) } @@ -809,6 +837,7 @@ func (s *UdmUserApi) UdmSubUserEdit(w http.ResponseWriter, r *http.Request) { if body.SmData != "" { msg += fmt.Sprintf(",sm_data=%s", body.SmData) } + msg += fmt.Sprintf(",static_ip=%s", body.StaticIp) // 发送MML data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg) @@ -819,7 +848,8 @@ func (s *UdmUserApi) UdmSubUserEdit(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.subUser.Update(neInfo.NeId, body) + neId = "-" + s.subUser.Update(neId, body) } ctx.JSON(w, 200, result.OkData(data)) } @@ -857,6 +887,7 @@ func (s *UdmUserApi) UdmSubUser4GIP(w http.ResponseWriter, r *http.Request) { } // 命令ok时 if strings.Contains(data, "ok") { + neId = "-" s.subUser.Update4GIP(neId, body) } ctx.JSON(w, 200, result.OkData(data)) @@ -896,6 +927,7 @@ func (s *UdmUserApi) UdmSubUserSmData(w http.ResponseWriter, r *http.Request) { } // 命令ok时 if strings.Contains(data, "ok") { + neId = "-" s.subUser.UpdateSmData(neId, body) } ctx.JSON(w, 200, result.OkData(data)) @@ -929,6 +961,7 @@ func (s *UdmUserApi) UdmSubUserRemove(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { + neId = "-" s.subUser.Delete(neId, imsi) } ctx.JSON(w, 200, result.OkData(data)) @@ -963,7 +996,8 @@ func (s *UdmUserApi) UdmSubUserRemoves(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { - s.authUser.Deletes(neId, imsi, num) + neId = "-" + s.subUser.Deletes(neId, imsi, num) } ctx.JSON(w, 200, result.OkData(data)) } @@ -977,6 +1011,7 @@ func (s *UdmUserApi) UdmSubUserExport(w http.ResponseWriter, r *http.Request) { ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) return } + neId = "-" list := s.subUser.List(model.UdmSubUser{NeID: neId}) // 文件名 fileName := fmt.Sprintf("OMC_AUTH_USER_EXPORT_%s_%d.csv", neId, time.Now().UnixMilli()) @@ -1006,6 +1041,17 @@ func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) { return } + // 获取文件名 + _, fileHeader, err := r.FormFile("file") + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + if !strings.HasSuffix(fileHeader.Filename, ".csv") { + ctx.JSON(w, 200, result.ErrMsg("请上传CSV格式文件,内容字段imsi, msisdn, ambr, nssai, arfb, sar,rat, cn, smf_sel, sm_dat,eps_dat")) + return + } + neInfo, err := NeInfoByUDM(neId) if err != nil { ctx.JSON(w, 200, result.ErrMsg(err.Error())) @@ -1013,7 +1059,7 @@ func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) { } // 文件名 - fileName := fmt.Sprintf("OMC_SUB_USER_IMPORT_%s_%d.csv", neId, time.Now().UnixMilli()) + fileName := fmt.Sprintf("OMC_SUB_USER_IMPORT_%s_%d_%s", neId, time.Now().UnixMilli(), fileHeader.Filename) filePath := fmt.Sprintf("%s/upload/mml/%s", conf.Get("ne.omcdir"), fileName) dstPath := conf.Get("mml.upload").(string) // 输出保存文件 @@ -1030,7 +1076,7 @@ func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) { return } - msg := fmt.Sprintf("import udmuser:path=%s", fmt.Sprintf("%s%s", dstPath, fileName)) + msg := fmt.Sprintf("import udmuser:path=%s", fmt.Sprintf("%s/%s", dstPath, fileName)) // 发送MML data, err := mmlclient.MMLSendMsgToString(neInfo.Ip, msg) @@ -1041,8 +1087,8 @@ func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) { // 命令ok时 if strings.Contains(data, "ok") { data := file.ReadCSVFile(filePath) - s.authUser.InsertCSV(neId, data) + neId = "-" + s.subUser.InsertCSV(neId, data) } - // ctx.JSON(w, 200, result.OkData(data)) - ctx.FileAttachment(w, r, filePath, fileName) + ctx.JSON(w, 200, result.OkData(data)) } diff --git a/features/udm_user/repo/repo_udm_sub_user.go b/features/udm_user/repo/repo_udm_sub_user.go index 3e72953a..230fd1a0 100644 --- a/features/udm_user/repo/repo_udm_sub_user.go +++ b/features/udm_user/repo/repo_udm_sub_user.go @@ -210,14 +210,16 @@ func (r *RepoUdmSubUser) Inserts(neID string, subUser model.UdmSubUser, num stri subUser.Imsi = fmt.Sprint(imsiV + i) subUser.Msisdn = fmt.Sprint(msisdnV + i) // IP会自动递增 - parts := strings.Split(subUser.StaticIp, ".") - lastPart := parts[3] - lastNum, _ := strconv.Atoi(lastPart) - lastNum += i - newLastPart := strconv.Itoa(lastNum) - parts[3] = newLastPart - newIP := strings.Join(parts, ".") - subUser.StaticIp = newIP + if subUser.StaticIp != "" { + parts := strings.Split(subUser.StaticIp, ".") + lastPart := parts[3] + lastNum, _ := strconv.Atoi(lastPart) + lastNum += i + newLastPart := strconv.Itoa(lastNum) + parts[3] = newLastPart + newIP := strings.Join(parts, ".") + subUser.StaticIp = newIP + } results, err := datasource.DefaultDB().Table("u_sub_user").Insert(subUser) if err == nil { insertNum += results @@ -289,6 +291,7 @@ func (r *RepoUdmSubUser) Update(neID string, authUser model.UdmSubUser) int64 { if authUser.EpsDat != "" && authUser.EpsDat != user.EpsDat { user.EpsDat = authUser.EpsDat } + user.StaticIp = authUser.StaticIp results, err := datasource.DefaultDB().Table("u_sub_user").Where("imsi = ? and ne_id = ?", user.Imsi, user.NeID).Update(user) if err != nil { diff --git a/features/udm_user/service/service_ne_ifno.go b/features/udm_user/service/service_ne_ifno.go deleted file mode 100644 index 0299fb6f..00000000 --- a/features/udm_user/service/service_ne_ifno.go +++ /dev/null @@ -1,15 +0,0 @@ -package service - -import ( - "ems.agt/lib/dborm" - "ems.agt/lib/log" -) - -func UDMNeInfo(neId string) (*dborm.NeInfo, error) { - neInfo, err := dborm.XormGetNeInfo("UDM", neId) - if err != nil { - log.Error("dborm.XormGetNeInfo is failed:", err) - return nil, err - } - return neInfo, nil -} diff --git a/features/udm_user/service/service_udm_auth_user.go b/features/udm_user/service/service_udm_auth_user.go index 82b5de23..c6651ba8 100644 --- a/features/udm_user/service/service_udm_auth_user.go +++ b/features/udm_user/service/service_udm_auth_user.go @@ -69,7 +69,7 @@ func (r *ServiceUdmAuthUser) InsertCSV(neID string, data []map[string]string) in if s, ok := v["opc"]; ok { authUser.Opc = s } - r.repoAuthUser.Insert(neID, authUser) + num += r.repoAuthUser.Insert(neID, authUser) } return num } diff --git a/features/udm_user/service/service_udm_sub_user.go b/features/udm_user/service/service_udm_sub_user.go index 2e8839a2..f265eacf 100644 --- a/features/udm_user/service/service_udm_sub_user.go +++ b/features/udm_user/service/service_udm_sub_user.go @@ -86,7 +86,7 @@ func (r *ServiceUdmSubUser) InsertCSV(neID string, data []map[string]string) int if s, ok := v["eps_dat"]; ok { subUser.EpsDat = s } - r.repoSunUser.Insert(neID, subUser) + num += r.repoSunUser.Insert(neID, subUser) } return num } diff --git a/lib/core/cmd/cmd.go b/lib/core/cmd/cmd.go new file mode 100644 index 00000000..085494d0 --- /dev/null +++ b/lib/core/cmd/cmd.go @@ -0,0 +1,201 @@ +package cmd + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "strings" + "time" +) + +func Exec(cmdStr string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecWithTimeOut(cmdStr string, timeout time.Duration) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecCronjobWithTimeOut(cmdStr string, workdir string, timeout time.Duration) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + cmd := exec.Command("bash", "-c", cmdStr) + cmd.Dir = workdir + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr:\n %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s \n\n; stdout:\n %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout:\n %s", stdout.String()) + } + } + return errMsg, err +} + +func Execf(cmdStr string, a ...interface{}) (string, error) { + cmd := exec.Command("bash", "-c", fmt.Sprintf(cmdStr, a...)) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecWithCheck(name string, a ...string) (string, error) { + cmd := exec.Command(name, a...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func ExecScript(scriptPath, workDir string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + cmd := exec.Command("bash", scriptPath) + cmd.Dir = workDir + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + if ctx.Err() == context.DeadlineExceeded { + return "", fmt.Errorf("errCmdTimeout %v", err) + } + if err != nil { + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err + } + return stdout.String(), nil +} + +func CheckIllegal(args ...string) bool { + if args == nil { + return false + } + for _, arg := range args { + if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") || + strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") || + strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") { + return true + } + } + return false +} + +func HasNoPasswordSudo() bool { + cmd2 := exec.Command("sudo", "-n", "ls") + err2 := cmd2.Run() + return err2 == nil +} + +func SudoHandleCmd() string { + cmd := exec.Command("sudo", "-n", "ls") + if err := cmd.Run(); err == nil { + return "sudo " + } + return "" +} + +func Which(name string) bool { + _, err := exec.LookPath(name) + return err == nil +} diff --git a/lib/core/mml_client/send.go b/lib/core/mml_client/send.go index 09a008f6..b8dc531a 100644 --- a/lib/core/mml_client/send.go +++ b/lib/core/mml_client/send.go @@ -49,7 +49,7 @@ func MMLSendMsgToString(ip, msg string) (string, error) { } // 命令成功 - if strings.HasPrefix(str, "command ok") { + if strings.Contains(str, "ok") { return str, nil } diff --git a/lib/core/utils/firewall/client.go b/lib/core/utils/firewall/client.go new file mode 100644 index 00000000..5e88704b --- /dev/null +++ b/lib/core/utils/firewall/client.go @@ -0,0 +1,34 @@ +package firewall + +import ( + "errors" + "os" + + "ems.agt/lib/core/utils/firewall/client" +) + +type FirewallClient interface { + Name() string // ufw firewalld + Start() error + Stop() error + Reload() error + Status() (string, error) // running not running + Version() (string, error) + + ListPort() ([]client.FireInfo, error) + ListAddress() ([]client.FireInfo, error) + + Port(port client.FireInfo, operation string) error + RichRules(rule client.FireInfo, operation string) error + PortForward(info client.Forward, operation string) error +} + +func NewFirewallClient() (FirewallClient, error) { + if _, err := os.Stat("/usr/sbin/firewalld"); err == nil { + return client.NewFirewalld() + } + if _, err := os.Stat("/usr/sbin/ufw"); err == nil { + return client.NewUfw() + } + return nil, errors.New("no such type") +} diff --git a/lib/core/utils/firewall/client/firewalld.go b/lib/core/utils/firewall/client/firewalld.go new file mode 100644 index 00000000..d026e4e9 --- /dev/null +++ b/lib/core/utils/firewall/client/firewalld.go @@ -0,0 +1,209 @@ +package client + +import ( + "fmt" + "strings" + "sync" + + "ems.agt/lib/core/cmd" +) + +type Firewall struct{} + +func NewFirewalld() (*Firewall, error) { + return &Firewall{}, nil +} + +func (f *Firewall) Name() string { + return "firewalld" +} + +func (f *Firewall) Status() (string, error) { + stdout, _ := cmd.Exec("firewall-cmd --state") + if stdout == "running\n" { + return "running", nil + } + return "not running", nil +} + +func (f *Firewall) Version() (string, error) { + stdout, err := cmd.Exec("firewall-cmd --version") + if err != nil { + return "", fmt.Errorf("load the firewall version failed, err: %s", stdout) + } + return strings.ReplaceAll(stdout, "\n ", ""), nil +} + +func (f *Firewall) Start() error { + stdout, err := cmd.Exec("systemctl start firewalld") + if err != nil { + return fmt.Errorf("enable the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Firewall) Stop() error { + stdout, err := cmd.Exec("systemctl stop firewalld") + if err != nil { + return fmt.Errorf("stop the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Firewall) Reload() error { + stdout, err := cmd.Exec("firewall-cmd --reload") + if err != nil { + return fmt.Errorf("reload firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Firewall) ListPort() ([]FireInfo, error) { + var wg sync.WaitGroup + var datas []FireInfo + wg.Add(2) + go func() { + defer wg.Done() + stdout, err := cmd.Exec("firewall-cmd --zone=public --list-ports") + if err != nil { + return + } + ports := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ") + for _, port := range ports { + if len(port) == 0 { + continue + } + var itemPort FireInfo + if strings.Contains(port, "/") { + itemPort.Port = strings.Split(port, "/")[0] + itemPort.Protocol = strings.Split(port, "/")[1] + } + itemPort.Strategy = "accept" + datas = append(datas, itemPort) + } + }() + + go func() { + defer wg.Done() + stdout1, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules") + if err != nil { + return + } + rules := strings.Split(stdout1, "\n") + for _, rule := range rules { + if len(rule) == 0 { + continue + } + itemRule := f.loadInfo(rule) + if len(itemRule.Port) != 0 && itemRule.Family == "ipv4" { + datas = append(datas, itemRule) + } + } + }() + wg.Wait() + return datas, nil +} + +func (f *Firewall) ListAddress() ([]FireInfo, error) { + stdout, err := cmd.Exec("firewall-cmd --zone=public --list-rich-rules") + if err != nil { + return nil, err + } + var datas []FireInfo + rules := strings.Split(stdout, "\n") + for _, rule := range rules { + if len(rule) == 0 { + continue + } + itemRule := f.loadInfo(rule) + if len(itemRule.Port) == 0 && len(itemRule.Address) != 0 { + datas = append(datas, itemRule) + } + } + return datas, nil +} + +func (f *Firewall) Port(port FireInfo, operation string) error { + if cmd.CheckIllegal(operation, port.Protocol, port.Port) { + return fmt.Errorf("errCmdIllegal %v", port) + } + + stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol) + if err != nil { + return fmt.Errorf("%s port failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Firewall) RichRules(rule FireInfo, operation string) error { + if cmd.CheckIllegal(operation, rule.Address, rule.Protocol, rule.Port, rule.Strategy) { + return fmt.Errorf("errCmdIllegal %v", rule) + } + ruleStr := "" + if strings.Contains(rule.Address, "-") { + std, err := cmd.Execf("firewall-cmd --permanent --new-ipset=%s --type=hash:ip", rule.Address) + if err != nil { + return fmt.Errorf("add new ipset failed, err: %s", std) + } + std2, err := cmd.Execf("firewall-cmd --permanent --ipset=%s --add-entry=%s", rule.Address, rule.Address) + if err != nil { + return fmt.Errorf("add entry to ipset failed, err: %s", std2) + } + if err := f.Reload(); err != nil { + return err + } + ruleStr = fmt.Sprintf("rule source ipset=%s %s", rule.Address, rule.Strategy) + } else { + ruleStr = "rule family=ipv4 " + if len(rule.Address) != 0 { + ruleStr += fmt.Sprintf("source address=%s ", rule.Address) + } + if len(rule.Port) != 0 { + ruleStr += fmt.Sprintf("port port=%s ", rule.Port) + } + if len(rule.Protocol) != 0 { + ruleStr += fmt.Sprintf("protocol=%s ", rule.Protocol) + } + ruleStr += rule.Strategy + } + stdout, err := cmd.Execf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleStr) + if err != nil { + return fmt.Errorf("%s rich rules failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Firewall) PortForward(info Forward, operation string) error { + ruleStr := fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Target) + if len(info.Address) != 0 { + ruleStr = fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Address, info.Target) + } + + stdout, err := cmd.Exec(ruleStr) + if err != nil { + return fmt.Errorf("%s port forward failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Firewall) loadInfo(line string) FireInfo { + var itemRule FireInfo + ruleInfo := strings.Split(strings.ReplaceAll(line, "\"", ""), " ") + for _, item := range ruleInfo { + switch { + case strings.Contains(item, "family="): + itemRule.Family = strings.ReplaceAll(item, "family=", "") + case strings.Contains(item, "ipset="): + itemRule.Address = strings.ReplaceAll(item, "ipset=", "") + case strings.Contains(item, "address="): + itemRule.Address = strings.ReplaceAll(item, "address=", "") + case strings.Contains(item, "port="): + itemRule.Port = strings.ReplaceAll(item, "port=", "") + case strings.Contains(item, "protocol="): + itemRule.Protocol = strings.ReplaceAll(item, "protocol=", "") + case item == "accept" || item == "drop" || item == "reject": + itemRule.Strategy = item + } + } + return itemRule +} diff --git a/lib/core/utils/firewall/client/info.go b/lib/core/utils/firewall/client/info.go new file mode 100644 index 00000000..86cc86e5 --- /dev/null +++ b/lib/core/utils/firewall/client/info.go @@ -0,0 +1,20 @@ +package client + +type FireInfo struct { + Family string `json:"family"` // ipv4 ipv6 + Address string `json:"address"` // Anywhere + Port string `json:"port"` + Protocol string `json:"protocol"` // tcp udp tcp/udp + Strategy string `json:"strategy"` // accept drop + + APPName string `json:"appName"` + IsUsed bool `json:"isUsed"` + Description string `json:"description"` +} + +type Forward struct { + Protocol string `json:"protocol"` + Address string `json:"address"` + Port string `json:"port"` + Target string `json:"target"` +} diff --git a/lib/core/utils/firewall/client/ufw.go b/lib/core/utils/firewall/client/ufw.go new file mode 100644 index 00000000..47294889 --- /dev/null +++ b/lib/core/utils/firewall/client/ufw.go @@ -0,0 +1,238 @@ +package client + +import ( + "fmt" + "strings" + + "ems.agt/lib/core/cmd" +) + +type Ufw struct { + CmdStr string +} + +func NewUfw() (*Ufw, error) { + var ufw Ufw + if cmd.HasNoPasswordSudo() { + ufw.CmdStr = "sudo ufw" + } else { + ufw.CmdStr = "ufw" + } + return &ufw, nil +} + +func (f *Ufw) Name() string { + return "ufw" +} + +func (f *Ufw) Status() (string, error) { + stdout, _ := cmd.Execf("%s status | grep Status", f.CmdStr) + if stdout == "Status: active\n" { + return "running", nil + } + stdout1, _ := cmd.Execf("%s status | grep 状态", f.CmdStr) + if stdout1 == "状态: 激活\n" { + return "running", nil + } + return "not running", nil +} + +func (f *Ufw) Version() (string, error) { + stdout, err := cmd.Execf("%s version | grep ufw", f.CmdStr) + if err != nil { + return "", fmt.Errorf("load the firewall status failed, err: %s", stdout) + } + info := strings.ReplaceAll(stdout, "\n", "") + return strings.ReplaceAll(info, "ufw ", ""), nil +} + +func (f *Ufw) Start() error { + stdout, err := cmd.Execf("echo y | %s enable", f.CmdStr) + if err != nil { + return fmt.Errorf("enable the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Ufw) Stop() error { + stdout, err := cmd.Execf("%s disable", f.CmdStr) + if err != nil { + return fmt.Errorf("stop the firewall failed, err: %s", stdout) + } + return nil +} + +func (f *Ufw) Reload() error { + return nil +} + +func (f *Ufw) ListPort() ([]FireInfo, error) { + stdout, err := cmd.Execf("%s status verbose", f.CmdStr) + if err != nil { + return nil, err + } + portInfos := strings.Split(stdout, "\n") + var datas []FireInfo + isStart := false + for _, line := range portInfos { + if strings.HasPrefix(line, "-") { + isStart = true + continue + } + if !isStart { + continue + } + itemFire := f.loadInfo(line, "port") + if len(itemFire.Port) != 0 && itemFire.Port != "Anywhere" && !strings.Contains(itemFire.Port, ".") { + itemFire.Port = strings.ReplaceAll(itemFire.Port, ":", "-") + datas = append(datas, itemFire) + } + } + return datas, nil +} + +func (f *Ufw) ListAddress() ([]FireInfo, error) { + stdout, err := cmd.Execf("%s status verbose", f.CmdStr) + if err != nil { + return nil, err + } + portInfos := strings.Split(stdout, "\n") + var datas []FireInfo + isStart := false + for _, line := range portInfos { + if strings.HasPrefix(line, "-") { + isStart = true + continue + } + if !isStart { + continue + } + if !strings.Contains(line, " IN") { + continue + } + itemFire := f.loadInfo(line, "address") + if strings.Contains(itemFire.Port, ".") { + itemFire.Address += ("-" + itemFire.Port) + itemFire.Port = "" + } + if len(itemFire.Port) == 0 && len(itemFire.Address) != 0 { + datas = append(datas, itemFire) + } + } + return datas, nil +} + +func (f *Ufw) Port(port FireInfo, operation string) error { + switch port.Strategy { + case "accept": + port.Strategy = "allow" + case "drop": + port.Strategy = "deny" + default: + return fmt.Errorf("unsupport strategy %s", port.Strategy) + } + if cmd.CheckIllegal(port.Protocol, port.Port) { + return fmt.Errorf("errCmdIllegal %v", port) + } + + command := fmt.Sprintf("%s %s %s", f.CmdStr, port.Strategy, port.Port) + if operation == "remove" { + command = fmt.Sprintf("%s delete %s %s", f.CmdStr, port.Strategy, port.Port) + } + if len(port.Protocol) != 0 { + command += fmt.Sprintf("/%s", port.Protocol) + } + stdout, err := cmd.Exec(command) + if err != nil { + return fmt.Errorf("%s port failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Ufw) RichRules(rule FireInfo, operation string) error { + switch rule.Strategy { + case "accept": + rule.Strategy = "allow" + case "drop": + rule.Strategy = "deny" + default: + return fmt.Errorf("unsupport strategy %s", rule.Strategy) + } + + if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) { + return fmt.Errorf("errCmdIllegal %v", rule) + } + + ruleStr := fmt.Sprintf("%s %s ", f.CmdStr, rule.Strategy) + if operation == "remove" { + ruleStr = fmt.Sprintf("%s delete %s ", f.CmdStr, rule.Strategy) + } + if len(rule.Protocol) != 0 { + ruleStr += fmt.Sprintf("proto %s ", rule.Protocol) + } + if strings.Contains(rule.Address, "-") { + ruleStr += fmt.Sprintf("from %s to %s ", strings.Split(rule.Address, "-")[0], strings.Split(rule.Address, "-")[1]) + } else { + ruleStr += fmt.Sprintf("from %s ", rule.Address) + } + if len(rule.Port) != 0 { + ruleStr += fmt.Sprintf("to any port %s ", rule.Port) + } + + stdout, err := cmd.Exec(ruleStr) + if err != nil { + return fmt.Errorf("%s rich rules failed, err: %s", operation, stdout) + } + return nil +} + +func (f *Ufw) PortForward(info Forward, operation string) error { + ruleStr := fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Target) + if len(info.Address) != 0 { + ruleStr = fmt.Sprintf("firewall-cmd --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.Address, info.Target) + } + + stdout, err := cmd.Exec(ruleStr) + if err != nil { + return fmt.Errorf("%s port forward failed, err: %s", operation, stdout) + } + if err := f.Reload(); err != nil { + return err + } + return nil +} + +func (f *Ufw) loadInfo(line string, fireType string) FireInfo { + fields := strings.Fields(line) + var itemInfo FireInfo + if len(fields) < 4 { + return itemInfo + } + if fields[1] == "(v6)" { + return itemInfo + } + if fields[0] == "Anywhere" && fireType != "port" { + itemInfo.Strategy = "drop" + if fields[1] == "ALLOW" { + itemInfo.Strategy = "accept" + } + itemInfo.Address = fields[3] + return itemInfo + } + if strings.Contains(fields[0], "/") { + itemInfo.Port = strings.Split(fields[0], "/")[0] + itemInfo.Protocol = strings.Split(fields[0], "/")[1] + } else { + itemInfo.Port = fields[0] + itemInfo.Protocol = "tcp/udp" + } + itemInfo.Family = "ipv4" + if fields[1] == "ALLOW" { + itemInfo.Strategy = "accept" + } else { + itemInfo.Strategy = "drop" + } + itemInfo.Address = fields[3] + + return itemInfo +} diff --git a/lib/core/utils/scan/scan.go b/lib/core/utils/scan/scan.go new file mode 100644 index 00000000..6313763a --- /dev/null +++ b/lib/core/utils/scan/scan.go @@ -0,0 +1,31 @@ +package scan + +import ( + "net" + "strconv" +) + +func ScanPort(port int) bool { + ln, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + return true + } + defer ln.Close() + return false +} + +func ScanUDPPort(port int) bool { + ln, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}) + if err != nil { + return true + } + defer ln.Close() + return false +} + +func ScanPortWithProto(port int, proto string) bool { + if proto == "udp" { + return ScanUDPPort(port) + } + return ScanPort(port) +}