Files
waf-platform/EdgeAPI/internal/rpc/services/service_ip_library.go

412 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"context"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"os"
"path/filepath"
"strings"
)
// IPLibraryService IP库服务
type IPLibraryService struct {
BaseService
}
// CreateIPLibrary 创建IP库
func (this *IPLibraryService) CreateIPLibrary(ctx context.Context, req *pb.CreateIPLibraryRequest) (*pb.CreateIPLibraryResponse, error) {
// 校验请求
_, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin)
if err != nil {
return nil, err
}
var tx = this.NullTx()
ipLibraryId, err := models.SharedIPLibraryDAO.CreateIPLibrary(tx, req.Type, req.FileId)
if err != nil {
return nil, err
}
return &pb.CreateIPLibraryResponse{
IpLibraryId: ipLibraryId,
}, nil
}
// FindEnabledIPLibrary 查找单个IP库
func (this *IPLibraryService) FindEnabledIPLibrary(ctx context.Context, req *pb.FindEnabledIPLibraryRequest) (*pb.FindEnabledIPLibraryResponse, error) {
// 校验请求
_, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin)
if err != nil {
return nil, err
}
var tx = this.NullTx()
ipLibrary, err := models.SharedIPLibraryDAO.FindEnabledIPLibrary(tx, req.IpLibraryId)
if err != nil {
return nil, err
}
if ipLibrary == nil {
return &pb.FindEnabledIPLibraryResponse{IpLibrary: nil}, nil
}
// 文件相关
var pbFile *pb.File = nil
file, err := models.SharedFileDAO.FindEnabledFile(tx, int64(ipLibrary.FileId))
if err != nil {
return nil, err
}
if file != nil {
pbFile = &pb.File{
Id: int64(file.Id),
Filename: file.Filename,
Size: int64(file.Size),
}
}
return &pb.FindEnabledIPLibraryResponse{
IpLibrary: &pb.IPLibrary{
Id: int64(ipLibrary.Id),
Type: ipLibrary.Type,
File: pbFile,
CreatedAt: int64(ipLibrary.CreatedAt),
},
}, nil
}
// FindLatestIPLibraryWithType 查找最新的IP库
func (this *IPLibraryService) FindLatestIPLibraryWithType(ctx context.Context, req *pb.FindLatestIPLibraryWithTypeRequest) (*pb.FindLatestIPLibraryWithTypeResponse, error) {
// 校验请求
_, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeNode)
if err != nil {
return nil, err
}
var tx = this.NullTx()
ipLibrary, err := models.SharedIPLibraryDAO.FindLatestIPLibraryWithType(tx, req.Type)
if err != nil {
return nil, err
}
if ipLibrary == nil {
return &pb.FindLatestIPLibraryWithTypeResponse{IpLibrary: nil}, nil
}
// 文件相关
var pbFile *pb.File = nil
file, err := models.SharedFileDAO.FindEnabledFile(tx, int64(ipLibrary.FileId))
if err != nil {
return nil, err
}
if file != nil {
pbFile = &pb.File{
Id: int64(file.Id),
Filename: file.Filename,
Size: int64(file.Size),
}
}
return &pb.FindLatestIPLibraryWithTypeResponse{
IpLibrary: &pb.IPLibrary{
Id: int64(ipLibrary.Id),
Type: ipLibrary.Type,
File: pbFile,
CreatedAt: int64(ipLibrary.CreatedAt),
},
}, nil
}
// FindAllEnabledIPLibrariesWithType 列出某个类型的所有IP库
func (this *IPLibraryService) FindAllEnabledIPLibrariesWithType(ctx context.Context, req *pb.FindAllEnabledIPLibrariesWithTypeRequest) (*pb.FindAllEnabledIPLibrariesWithTypeResponse, error) {
// 校验请求
_, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin)
if err != nil {
return nil, err
}
var tx = this.NullTx()
ipLibraries, err := models.SharedIPLibraryDAO.FindAllEnabledIPLibrariesWithType(tx, req.Type)
if err != nil {
return nil, err
}
result := []*pb.IPLibrary{}
for _, library := range ipLibraries {
// 文件相关
var pbFile *pb.File = nil
file, err := models.SharedFileDAO.FindEnabledFile(tx, int64(library.FileId))
if err != nil {
return nil, err
}
if file != nil {
pbFile = &pb.File{
Id: int64(file.Id),
Filename: file.Filename,
Size: int64(file.Size),
}
}
result = append(result, &pb.IPLibrary{
Id: int64(library.Id),
Type: library.Type,
File: pbFile,
CreatedAt: int64(library.CreatedAt),
})
}
return &pb.FindAllEnabledIPLibrariesWithTypeResponse{IpLibraries: result}, nil
}
// DeleteIPLibrary 删除IP库
func (this *IPLibraryService) DeleteIPLibrary(ctx context.Context, req *pb.DeleteIPLibraryRequest) (*pb.RPCSuccess, error) {
// 校验请求
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedIPLibraryDAO.DisableIPLibrary(tx, req.IpLibraryId)
if err != nil {
return nil, err
}
return this.Success()
}
// LookupIPRegion 查询某个IP信息
func (this *IPLibraryService) LookupIPRegion(ctx context.Context, req *pb.LookupIPRegionRequest) (*pb.LookupIPRegionResponse, error) {
// 校验请求
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
var result = iplibrary.LookupIP(req.Ip)
if result == nil || !result.IsOk() {
return &pb.LookupIPRegionResponse{IpRegion: nil}, nil
}
return &pb.LookupIPRegionResponse{IpRegion: &pb.IPRegion{
Country: result.CountryName(),
Region: "",
Province: result.ProvinceName(),
City: result.CityName(),
Isp: result.ProviderName(),
CountryId: result.CountryId(),
ProvinceId: result.ProvinceId(),
CityId: result.CityId(),
TownId: result.TownId(),
ProviderId: result.ProviderId(),
Summary: result.Summary(),
}}, nil
}
// LookupIPRegions 查询一组IP信息
func (this *IPLibraryService) LookupIPRegions(ctx context.Context, req *pb.LookupIPRegionsRequest) (*pb.LookupIPRegionsResponse, error) {
// 校验请求
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
var result = map[string]*pb.IPRegion{}
if len(req.IpList) > 0 {
for _, ip := range req.IpList {
var info = iplibrary.LookupIP(ip)
if info != nil && info.IsOk() {
result[ip] = &pb.IPRegion{
Country: info.CountryName(),
Region: "",
Province: info.ProvinceName(),
City: info.CityName(),
Isp: info.ProviderName(),
CountryId: info.CountryId(),
ProvinceId: info.ProvinceId(),
CityId: info.CityId(),
TownId: info.TownId(),
ProviderId: info.ProviderId(),
Summary: info.Summary(),
}
}
}
}
return &pb.LookupIPRegionsResponse{IpRegionMap: result}, nil
}
// ReloadIPLibrary 重新加载IP库
func (this *IPLibraryService) ReloadIPLibrary(ctx context.Context, req *pb.ReloadIPLibraryRequest) (*pb.RPCSuccess, error) {
// 校验请求
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
// 重新加载IP库
err = iplibrary.InitDefault()
if err != nil {
return nil, err
}
return this.Success()
}
// UploadMaxMindFile 上传MaxMind文件到EdgeAPI
func (this *IPLibraryService) UploadMaxMindFile(ctx context.Context, req *pb.UploadMaxMindFileRequest) (*pb.RPCSuccess, error) {
// 校验请求
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if len(req.Filename) == 0 || len(req.Data) == 0 {
return nil, errors.New("filename and data are required")
}
// 检查文件名
filename := strings.ToLower(req.Filename)
if !strings.HasSuffix(filename, ".mmdb") {
return nil, errors.New("only MaxMind format files (.mmdb) are supported")
}
// 确定目标路径
iplibDir := Tea.Root + "/data/iplibrary"
err = os.MkdirAll(iplibDir, 0755)
if err != nil {
return nil, fmt.Errorf("create IP library directory failed: %w", err)
}
var targetPath string
if strings.Contains(filename, "city") {
targetPath = filepath.Join(iplibDir, "maxmind-city.mmdb")
} else if strings.Contains(filename, "asn") {
targetPath = filepath.Join(iplibDir, "maxmind-asn.mmdb")
} else {
return nil, errors.New("MaxMind filename must contain 'city' or 'asn'")
}
// 保存文件(使用临时文件原子替换)
tmpPath := targetPath + ".tmp"
err = os.WriteFile(tmpPath, req.Data, 0644)
if err != nil {
return nil, fmt.Errorf("save IP library file failed: %w", err)
}
// 原子替换
err = os.Rename(tmpPath, targetPath)
if err != nil {
os.Remove(tmpPath)
return nil, fmt.Errorf("replace IP library file failed: %w", err)
}
// 重新加载IP库
err = iplibrary.InitDefault()
if err != nil {
return nil, fmt.Errorf("reload IP library failed: %w", err)
}
return this.Success()
}
// FindMaxMindFileStatus 查询MaxMind文件状态
func (this *IPLibraryService) FindMaxMindFileStatus(ctx context.Context, req *pb.FindMaxMindFileStatusRequest) (*pb.FindMaxMindFileStatusResponse, error) {
// 校验请求
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
// 检查EdgeAPI的data/iplibrary/目录
// 使用与 UploadMaxMindFile 相同的路径逻辑
iplibDir := Tea.Root + "/data/iplibrary"
cityDBPath := filepath.Join(iplibDir, "maxmind-city.mmdb")
asnDBPath := filepath.Join(iplibDir, "maxmind-asn.mmdb")
cityExists := false
asnExists := false
// 检查文件是否存在
if stat, err := os.Stat(cityDBPath); err == nil && stat != nil && !stat.IsDir() {
cityExists = true
}
// 文件不存在是正常情况(会使用嵌入的库),不需要记录错误
if stat, err := os.Stat(asnDBPath); err == nil && stat != nil && !stat.IsDir() {
asnExists = true
}
// 检查是否使用了MaxMind库通过测试查询来判断
testIP := "8.8.8.8"
testResult := iplibrary.LookupIP(testIP)
usingMaxMind := false
if testResult != nil && testResult.IsOk() {
// MaxMind库的特征CountryId 和 ProvinceId 通常为 0因为MaxMind不使用ID系统
// 同时有国家名称,说明查询成功
if testResult.CountryId() == 0 && len(testResult.CountryName()) > 0 {
usingMaxMind = true
}
}
usingEmbeddedMaxMind := usingMaxMind && !cityExists
return &pb.FindMaxMindFileStatusResponse{
CityExists: cityExists,
AsnExists: asnExists,
UsingMaxMind: usingMaxMind,
UsingEmbeddedMaxMind: usingEmbeddedMaxMind,
}, nil
}
// DeleteMaxMindFile 删除MaxMind文件
func (this *IPLibraryService) DeleteMaxMindFile(ctx context.Context, req *pb.DeleteMaxMindFileRequest) (*pb.RPCSuccess, error) {
// 校验请求
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
iplibDir := Tea.Root + "/data/iplibrary"
cityDBPath := filepath.Join(iplibDir, "maxmind-city.mmdb")
asnDBPath := filepath.Join(iplibDir, "maxmind-asn.mmdb")
// 根据文件名删除对应的文件,如果为空则删除所有
filename := strings.ToLower(req.Filename)
if len(filename) == 0 {
// 删除所有文件
if err := os.Remove(cityDBPath); err != nil && !os.IsNotExist(err) {
remotelogs.Error("IP_LIBRARY", "delete city file failed: "+err.Error())
}
if err := os.Remove(asnDBPath); err != nil && !os.IsNotExist(err) {
remotelogs.Error("IP_LIBRARY", "delete ASN file failed: "+err.Error())
}
} else if strings.Contains(filename, "city") {
// 只删除 City 文件
if err := os.Remove(cityDBPath); err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("delete city file failed: %w", err)
}
} else if strings.Contains(filename, "asn") {
// 只删除 ASN 文件
if err := os.Remove(asnDBPath); err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("delete ASN file failed: %w", err)
}
} else {
return nil, errors.New("filename must contain 'city' or 'asn', or be empty to delete all")
}
// 重新加载IP库使用嵌入的默认库
err = iplibrary.InitDefault()
if err != nil {
remotelogs.Error("IP_LIBRARY", "reload IP library after deletion failed: "+err.Error())
// 不返回错误,因为文件已经删除成功
}
return this.Success()
}